@onekeyfe/hd-core 1.2.2-alpha.0 → 1.2.2-alpha.10
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.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +2 -2
- package/__tests__/device-lifecycle-events.test.ts +67 -0
- package/__tests__/deviceUploadNft.test.ts +27 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +322 -21
- package/__tests__/open-wallet-session.test.ts +613 -80
- package/__tests__/pro2HostAssetPackage.test.ts +108 -1
- package/__tests__/protocol-v2.test.ts +231 -49
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/dist/api/FirmwareUpdateV4.d.ts +2 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +2 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +1454 -1144
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +73 -23
- package/src/api/OpenWalletSession.ts +0 -3
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +3 -1
- package/src/api/helpers/protocolV2FileWrite.ts +11 -5
- package/src/api/protocol-v2/DeviceUploadNft.ts +4 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +17 -3
- package/src/core/RequestQueue.ts +16 -1
- package/src/core/index.ts +48 -21
- package/src/data/messages/messages-protocol-v2.json +1471 -1367
- package/src/protocols/protocol-v2/walletSession.ts +157 -38
- package/src/utils/pro2HostAssetPackage.ts +143 -58
|
@@ -374,6 +374,22 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
374
374
|
);
|
|
375
375
|
};
|
|
376
376
|
|
|
377
|
+
const isProtocolV2InstallResponseTimeout = (error: unknown) => {
|
|
378
|
+
if (!error || typeof error !== 'object') return false;
|
|
379
|
+
const { code } = error as { code?: number | string };
|
|
380
|
+
return (
|
|
381
|
+
code === 'response-timeout' ||
|
|
382
|
+
/\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error))
|
|
383
|
+
);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const getProtocolV2ErrorCode = (error: unknown): number | string | undefined => {
|
|
387
|
+
if (!error || typeof error !== 'object') return undefined;
|
|
388
|
+
const { errorCode, code } = error as { errorCode?: unknown; code?: unknown };
|
|
389
|
+
const causeCode = errorCode ?? code;
|
|
390
|
+
return typeof causeCode === 'number' || typeof causeCode === 'string' ? causeCode : undefined;
|
|
391
|
+
};
|
|
392
|
+
|
|
377
393
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
378
394
|
isBleStaleBondHardwareError(error) ||
|
|
379
395
|
(error instanceof HardwareError &&
|
|
@@ -565,6 +581,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
565
581
|
|
|
566
582
|
private protocolV2InstallNeedsReconnect = false;
|
|
567
583
|
|
|
584
|
+
private protocolV2InstallDisconnectObserved = false;
|
|
585
|
+
|
|
568
586
|
private protocolV2InstallTerminalSuccessObserved = false;
|
|
569
587
|
|
|
570
588
|
private protocolV2LastRuntimeProbeFeatures?: Features;
|
|
@@ -573,6 +591,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
573
591
|
|
|
574
592
|
private protocolV2LastTransferProgressAt = 0;
|
|
575
593
|
|
|
594
|
+
private protocolV2LastTransferredBytes = 0;
|
|
595
|
+
|
|
576
596
|
init() {
|
|
577
597
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
578
598
|
this.requireDeviceMode = [];
|
|
@@ -2103,6 +2123,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2103
2123
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
2104
2124
|
this.protocolV2LastTransferProgress = undefined;
|
|
2105
2125
|
this.protocolV2LastTransferProgressAt = 0;
|
|
2126
|
+
this.protocolV2LastTransferredBytes = 0;
|
|
2127
|
+
const transferStartedAt = Date.now();
|
|
2106
2128
|
let processedSize = 0;
|
|
2107
2129
|
for (const resource of resourcesToSync) {
|
|
2108
2130
|
// The bootloader keeps its live resource package mounted. FatFs rejects
|
|
@@ -2113,6 +2135,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2113
2135
|
filePath: writePath,
|
|
2114
2136
|
processedSize,
|
|
2115
2137
|
totalSize,
|
|
2138
|
+
transferStartedAt,
|
|
2116
2139
|
});
|
|
2117
2140
|
await this.verifyProtocolV2StagedFile(writePath, resource.source.size);
|
|
2118
2141
|
if (isProtocolV2BootResourcePackagePath(resource.devicePath)) {
|
|
@@ -2128,6 +2151,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2128
2151
|
filePath,
|
|
2129
2152
|
processedSize,
|
|
2130
2153
|
totalSize,
|
|
2154
|
+
transferStartedAt,
|
|
2131
2155
|
});
|
|
2132
2156
|
await this.verifyProtocolV2StagedFile(filePath, item.source.size);
|
|
2133
2157
|
stagedInstallTargets.push({
|
|
@@ -2137,7 +2161,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2137
2161
|
}
|
|
2138
2162
|
|
|
2139
2163
|
if (totalSize > 0) {
|
|
2140
|
-
|
|
2164
|
+
const elapsedMs = Math.max(Date.now() - transferStartedAt, 0);
|
|
2165
|
+
this.postProgressMessage(100, 'transferData', {
|
|
2166
|
+
transferredBytes: totalSize,
|
|
2167
|
+
totalBytes: totalSize,
|
|
2168
|
+
rateBytesPerSecond: elapsedMs > 0 ? Math.round((totalSize / elapsedMs) * 1000) : undefined,
|
|
2169
|
+
elapsedMs,
|
|
2170
|
+
});
|
|
2141
2171
|
}
|
|
2142
2172
|
if (stagedInstallTargets.length === 0) {
|
|
2143
2173
|
return;
|
|
@@ -2162,20 +2192,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2162
2192
|
filePath,
|
|
2163
2193
|
processedSize,
|
|
2164
2194
|
totalSize,
|
|
2195
|
+
transferStartedAt = Date.now(),
|
|
2165
2196
|
}: {
|
|
2166
2197
|
source: FirmwareByteSource;
|
|
2167
2198
|
filePath: string;
|
|
2168
2199
|
processedSize: number;
|
|
2169
2200
|
totalSize: number;
|
|
2201
|
+
transferStartedAt?: number;
|
|
2170
2202
|
}) {
|
|
2171
2203
|
let lastError: unknown;
|
|
2172
2204
|
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
2205
|
+
this.throwIfAborted();
|
|
2173
2206
|
try {
|
|
2174
|
-
const transferStartedAt = Date.now();
|
|
2175
2207
|
await writeFirmwareByteSource({
|
|
2176
2208
|
source,
|
|
2177
2209
|
chunkSize: this.getProtocolV2FirmwareChunkSize('write', filePath),
|
|
2178
2210
|
write: async ({ data, sourceOffset, length, first }) => {
|
|
2211
|
+
this.throwIfAborted();
|
|
2179
2212
|
const chunkEnd = sourceOffset + length;
|
|
2180
2213
|
const deviceProgress = getProtocolV2DeviceTransferProgress(
|
|
2181
2214
|
processedSize + sourceOffset,
|
|
@@ -2203,18 +2236,20 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2203
2236
|
const elapsedMs = Math.max(now - transferStartedAt, 0);
|
|
2204
2237
|
const progress = Math.min(Math.ceil((transferredBytes / totalSize) * 100), 99);
|
|
2205
2238
|
const shouldPostProgress =
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2239
|
+
transferredBytes >= this.protocolV2LastTransferredBytes &&
|
|
2240
|
+
(progress !== this.protocolV2LastTransferProgress ||
|
|
2241
|
+
now - this.protocolV2LastTransferProgressAt >=
|
|
2242
|
+
PROTOCOL_V2_TRANSFER_PROGRESS_HEARTBEAT_MS ||
|
|
2243
|
+
chunkEnd === source.size);
|
|
2210
2244
|
if (shouldPostProgress) {
|
|
2211
2245
|
this.protocolV2LastTransferProgress = progress;
|
|
2212
2246
|
this.protocolV2LastTransferProgressAt = now;
|
|
2247
|
+
this.protocolV2LastTransferredBytes = transferredBytes;
|
|
2213
2248
|
this.postProgressMessage(progress, 'transferData', {
|
|
2214
2249
|
transferredBytes,
|
|
2215
2250
|
totalBytes: totalSize,
|
|
2216
2251
|
rateBytesPerSecond:
|
|
2217
|
-
elapsedMs > 0 ? Math.round((
|
|
2252
|
+
elapsedMs > 0 ? Math.round((transferredBytes / elapsedMs) * 1000) : undefined,
|
|
2218
2253
|
elapsedMs,
|
|
2219
2254
|
});
|
|
2220
2255
|
}
|
|
@@ -2223,18 +2258,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2223
2258
|
});
|
|
2224
2259
|
return processedSize + source.size;
|
|
2225
2260
|
} catch (error) {
|
|
2261
|
+
this.throwIfAborted();
|
|
2226
2262
|
if (isBleStaleBondHardwareError(error)) {
|
|
2227
2263
|
throw error;
|
|
2228
2264
|
}
|
|
2229
2265
|
lastError = error;
|
|
2230
2266
|
if (attempt < PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
2231
2267
|
await this.recoverProtocolV2FileTransfer();
|
|
2268
|
+
this.throwIfAborted();
|
|
2232
2269
|
}
|
|
2233
2270
|
}
|
|
2234
2271
|
}
|
|
2272
|
+
const causeCode = getProtocolV2ErrorCode(lastError);
|
|
2235
2273
|
throw ERRORS.TypedError(
|
|
2236
2274
|
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
2237
|
-
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}
|
|
2275
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`,
|
|
2276
|
+
causeCode === undefined ? undefined : { causeCode }
|
|
2238
2277
|
);
|
|
2239
2278
|
}
|
|
2240
2279
|
|
|
@@ -2517,9 +2556,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2517
2556
|
) {
|
|
2518
2557
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
2519
2558
|
const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
|
|
2520
|
-
const
|
|
2521
|
-
this.isBleReconnect() &&
|
|
2522
|
-
expectedTargetIds.has(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR);
|
|
2559
|
+
const isBleInstall = this.isBleReconnect();
|
|
2523
2560
|
const startTime = Date.now();
|
|
2524
2561
|
let lastError: unknown;
|
|
2525
2562
|
// The loader may release either USB or BLE as installation starts. Recovery reconnects
|
|
@@ -2528,7 +2565,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2528
2565
|
this.protocolV2InstallNeedsReconnect = false;
|
|
2529
2566
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2530
2567
|
let bleInstallLinkReady = false;
|
|
2531
|
-
let installStatusDisconnectObserved =
|
|
2568
|
+
let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
|
|
2569
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
2532
2570
|
let installReconnectObserved = false;
|
|
2533
2571
|
let finishedStatusSnapshotKey: string | undefined;
|
|
2534
2572
|
let finishedStatusSnapshotPolls = 0;
|
|
@@ -2536,6 +2574,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2536
2574
|
let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
|
|
2537
2575
|
let currentInstallStatusObserved = false;
|
|
2538
2576
|
const liveTargetIds = new Set<number>();
|
|
2577
|
+
const recordBleInstallDisconnect = (error: unknown) => {
|
|
2578
|
+
if (
|
|
2579
|
+
!isBleInstall ||
|
|
2580
|
+
!isProtocolV2DeviceDisconnectedError(error) ||
|
|
2581
|
+
isProtocolV2InstallResponseTimeout(error)
|
|
2582
|
+
) {
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
installStatusDisconnectObserved = true;
|
|
2586
|
+
installReconnectObserved = false;
|
|
2587
|
+
finishedStatusSnapshotKey = undefined;
|
|
2588
|
+
finishedStatusSnapshotPolls = 0;
|
|
2589
|
+
};
|
|
2539
2590
|
|
|
2540
2591
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2541
2592
|
// A transport release caused by an explicit workflow cancellation must not
|
|
@@ -2682,7 +2733,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2682
2733
|
if (!hasCurrentInstallEvidence) {
|
|
2683
2734
|
// A successful status poll clears DeviceCommands' cancel action. Keep cancellation
|
|
2684
2735
|
// available while UpdateRequest is still waiting for confirmation on the device.
|
|
2685
|
-
|
|
2736
|
+
// Bootloader does not register the protocol Cancel message. Keep the
|
|
2737
|
+
// operation locally cancellable; interruptionFromUser() disposes the link.
|
|
2738
|
+
this.device.setCancelableAction(() => Promise.resolve());
|
|
2686
2739
|
}
|
|
2687
2740
|
|
|
2688
2741
|
if (
|
|
@@ -2787,15 +2840,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2787
2840
|
);
|
|
2788
2841
|
}
|
|
2789
2842
|
} else {
|
|
2790
|
-
|
|
2791
|
-
isBleCoprocessorInstall &&
|
|
2792
|
-
isProtocolV2DeviceDisconnectedError(error) &&
|
|
2793
|
-
!isProtocolV2ResponseTimeout(error)
|
|
2794
|
-
) {
|
|
2795
|
-
installStatusDisconnectObserved = true;
|
|
2796
|
-
finishedStatusSnapshotKey = undefined;
|
|
2797
|
-
finishedStatusSnapshotPolls = 0;
|
|
2798
|
-
}
|
|
2843
|
+
recordBleInstallDisconnect(error);
|
|
2799
2844
|
shouldReconnect = true;
|
|
2800
2845
|
deviceInfo = undefined;
|
|
2801
2846
|
bleInstallLinkReady = false;
|
|
@@ -2820,6 +2865,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2820
2865
|
) {
|
|
2821
2866
|
throw error;
|
|
2822
2867
|
}
|
|
2868
|
+
recordBleInstallDisconnect(error);
|
|
2823
2869
|
shouldReconnect = true;
|
|
2824
2870
|
deviceInfo = undefined;
|
|
2825
2871
|
bleInstallLinkReady = false;
|
|
@@ -3154,10 +3200,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3154
3200
|
}) {
|
|
3155
3201
|
this.protocolV2LastRuntimeProbeFeatures = undefined;
|
|
3156
3202
|
this.protocolV2InstallNeedsReconnect = false;
|
|
3203
|
+
this.protocolV2InstallDisconnectObserved = false;
|
|
3157
3204
|
this.protocolV2InstallTerminalSuccessObserved = false;
|
|
3158
3205
|
const commands = this.device.getCommands();
|
|
3159
3206
|
await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
|
|
3160
|
-
|
|
3207
|
+
// Bootloader does not register the protocol Cancel message. Keep the
|
|
3208
|
+
// operation locally cancellable; interruptionFromUser() disposes the link.
|
|
3209
|
+
this.device.setCancelableAction(() => Promise.resolve());
|
|
3161
3210
|
const interaction = this.device.createProtocolV2UiPhaseMetadata('button', 'start');
|
|
3162
3211
|
this.postMessage(
|
|
3163
3212
|
createUiMessage(UI_REQUEST.REQUEST_BUTTON, {
|
|
@@ -3196,6 +3245,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
3196
3245
|
'[FirmwareUpdateV4] transport released while writing install request; continue status polling'
|
|
3197
3246
|
);
|
|
3198
3247
|
this.protocolV2InstallNeedsReconnect = true;
|
|
3248
|
+
this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
|
|
3199
3249
|
}
|
|
3200
3250
|
}
|
|
3201
3251
|
|
|
@@ -160,7 +160,6 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
160
160
|
const session = isProtocolV2
|
|
161
161
|
? await getProtocolV2WalletSession(this.device, {
|
|
162
162
|
onlyMainPin: true,
|
|
163
|
-
deriveCardano: this.payload.deriveCardano,
|
|
164
163
|
selectMainWalletBeforeRestore:
|
|
165
164
|
!hasAuthoritativeProtocolV2WalletStatus(state) ||
|
|
166
165
|
state.status.unlockedAttachPin === true,
|
|
@@ -225,7 +224,6 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
225
224
|
const session = isProtocolV2
|
|
226
225
|
? await getProtocolV2WalletSession(this.device, {
|
|
227
226
|
expectedPassphraseState: this.params.passphraseState,
|
|
228
|
-
deriveCardano: this.payload.deriveCardano,
|
|
229
227
|
})
|
|
230
228
|
: await getPassphraseStateWithRefreshDeviceInfo(this.device, {
|
|
231
229
|
expectPassphraseState: this.params.passphraseState,
|
|
@@ -257,7 +255,6 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
|
|
|
257
255
|
...(readCurrentAttachPinSession
|
|
258
256
|
? { readCurrentAttachPinSession: true }
|
|
259
257
|
: { forceWalletSelection: true }),
|
|
260
|
-
deriveCardano: this.payload.deriveCardano,
|
|
261
258
|
})
|
|
262
259
|
: await getPassphraseStateWithRefreshDeviceInfo(this.device, { initSession: true });
|
|
263
260
|
const refreshedState = isProtocolV2
|
|
@@ -393,7 +393,9 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
|
|
|
393
393
|
// the root fingerprint, so each nested chain method must resume the
|
|
394
394
|
// requested standard or hidden wallet before sending its device command.
|
|
395
395
|
const useEmptyPassphrase = this.payload.useEmptyPassphrase === true;
|
|
396
|
-
|
|
396
|
+
// Nested Cardano methods opt in to [Standard, Cardano] if Ask rebuilds.
|
|
397
|
+
// Other chains stay Standard-only.
|
|
398
|
+
const deriveCardano = method.name.startsWith('cardano') ? true : undefined;
|
|
397
399
|
const shouldResumeWalletSession = useEmptyPassphrase || !!this.payload.passphraseState;
|
|
398
400
|
if (this.device.isProtocolV2() && shouldResumeWalletSession) {
|
|
399
401
|
const passphraseStateSafety = await this.device.checkPassphraseStateSafety(
|
|
@@ -36,6 +36,8 @@ export type ProtocolV2FileWriteOptions = {
|
|
|
36
36
|
chunkSize?: number;
|
|
37
37
|
chunkLen?: number;
|
|
38
38
|
chunkSizeLimit?: number;
|
|
39
|
+
/** BLE-only limit for a caller whose fixed short path has a verified larger frame budget. */
|
|
40
|
+
bleChunkSizeLimit?: number;
|
|
39
41
|
overwrite?: boolean;
|
|
40
42
|
append?: boolean;
|
|
41
43
|
uiPercentage?: number;
|
|
@@ -114,11 +116,15 @@ export function isProtocolV2ResponseTimeout(error: unknown) {
|
|
|
114
116
|
);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
function getProtocolV2FileChunkLimit() {
|
|
119
|
+
function getProtocolV2FileChunkLimit(bleChunkSizeLimit?: number) {
|
|
118
120
|
const env = DataManager.getSettings('env');
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
if (env && DataManager.isBleConnect(env)) {
|
|
122
|
+
const configuredLimit = Number(bleChunkSizeLimit);
|
|
123
|
+
return Number.isFinite(configuredLimit) && configuredLimit > 0
|
|
124
|
+
? Math.floor(configuredLimit)
|
|
125
|
+
: PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
|
|
126
|
+
}
|
|
127
|
+
return PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
async function dataToUint8Array(data: ProtocolV2FileWriteData): Promise<Uint8Array> {
|
|
@@ -178,7 +184,7 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
|
|
|
178
184
|
);
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
const defaultChunkSizeLimit = getProtocolV2FileChunkLimit();
|
|
187
|
+
const defaultChunkSizeLimit = getProtocolV2FileChunkLimit(options.bleChunkSizeLimit);
|
|
182
188
|
const configuredChunkSizeLimit = Number(options.chunkSizeLimit);
|
|
183
189
|
const chunkSizeLimit =
|
|
184
190
|
Number.isFinite(configuredChunkSizeLimit) && configuredChunkSizeLimit > 0
|
|
@@ -42,8 +42,11 @@ export type DeviceUploadNftParams = {
|
|
|
42
42
|
|
|
43
43
|
export type DeviceUploadNftResponse = {
|
|
44
44
|
basename: string;
|
|
45
|
+
/** Final image path after firmware extracts a host-asset package. */
|
|
45
46
|
imagePath: string;
|
|
47
|
+
/** Final thumbnail path after firmware extracts a host-asset package. */
|
|
46
48
|
thumbnailPath: string;
|
|
49
|
+
/** Final metadata path after firmware extracts a host-asset package. */
|
|
47
50
|
metadataPath: string;
|
|
48
51
|
totalSize: number;
|
|
49
52
|
nftUpdated: true;
|
|
@@ -197,7 +200,7 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
|
|
|
197
200
|
if (!bundle) throw invalidParameter('NFT data has not been initialized.');
|
|
198
201
|
|
|
199
202
|
const useHostAssetPackage = supportsPro2HostAssetPackage(
|
|
200
|
-
this.device.
|
|
203
|
+
this.device.state?.versions.firmware ?? undefined
|
|
201
204
|
);
|
|
202
205
|
await this.assertCapabilities(useHostAssetPackage);
|
|
203
206
|
this.throwIfAborted();
|
|
@@ -23,11 +23,19 @@ import {
|
|
|
23
23
|
|
|
24
24
|
export type DeviceUploadWallpaperParams = {
|
|
25
25
|
jpegBase64: string;
|
|
26
|
+
/**
|
|
27
|
+
* Legacy firmware uses this name for the uploaded `.bin` file. Firmware
|
|
28
|
+
* 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
|
|
29
|
+
*/
|
|
26
30
|
fileName?: string;
|
|
27
31
|
chunkSize?: number;
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
export type DeviceUploadWallpaperResponse = {
|
|
35
|
+
/**
|
|
36
|
+
* Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
|
|
37
|
+
* the temporary package path; firmware extracts and persists wallpaper.bin.
|
|
38
|
+
*/
|
|
31
39
|
path: string;
|
|
32
40
|
size: number;
|
|
33
41
|
colorFormat: Pro2WallpaperColorFormat;
|
|
@@ -41,6 +49,7 @@ const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
|
|
|
41
49
|
const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
|
|
42
50
|
const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
|
|
43
51
|
const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
|
|
52
|
+
const WALLPAPER_PACKAGE_BLE_CHUNK_SIZE = 1960;
|
|
44
53
|
const Log = getLogger(LoggerNames.Method);
|
|
45
54
|
|
|
46
55
|
function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
|
|
@@ -122,7 +131,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
122
131
|
this.directoryReady = true;
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
private async upload(path: string, data: Uint8Array) {
|
|
134
|
+
private async upload(path: string, data: Uint8Array, bleChunkSizeLimit?: number) {
|
|
126
135
|
if (this.uploaded) return;
|
|
127
136
|
|
|
128
137
|
await writeProtocolV2File({
|
|
@@ -131,6 +140,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
131
140
|
data,
|
|
132
141
|
totalSize: data.byteLength,
|
|
133
142
|
chunkSize: this.params.chunkSize,
|
|
143
|
+
bleChunkSizeLimit,
|
|
134
144
|
maxChunkRetries: 3,
|
|
135
145
|
overwrite: true,
|
|
136
146
|
append: false,
|
|
@@ -150,13 +160,17 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
150
160
|
await this.assertCapabilities();
|
|
151
161
|
await this.ensureDirectory();
|
|
152
162
|
const useHostAssetPackage = supportsPro2HostAssetPackage(
|
|
153
|
-
this.device.
|
|
163
|
+
this.device.state?.versions.firmware ?? undefined
|
|
154
164
|
);
|
|
155
165
|
const data = useHostAssetPackage
|
|
156
166
|
? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
|
|
157
167
|
: encoded.data;
|
|
158
168
|
if (useHostAssetPackage) this.path = WALLPAPER_PACKAGE_PATH;
|
|
159
|
-
await this.upload(
|
|
169
|
+
await this.upload(
|
|
170
|
+
this.path,
|
|
171
|
+
data,
|
|
172
|
+
useHostAssetPackage ? WALLPAPER_PACKAGE_BLE_CHUNK_SIZE : undefined
|
|
173
|
+
);
|
|
160
174
|
const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
|
|
161
175
|
settings: { wallpaper_path: this.path },
|
|
162
176
|
});
|
package/src/core/RequestQueue.ts
CHANGED
|
@@ -56,11 +56,20 @@ export default class RequestQueue {
|
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
private isRequestForConnectId(request: RequestTask, connectId: string) {
|
|
60
|
+
const { method } = request;
|
|
61
|
+
return (
|
|
62
|
+
method.connectId === connectId ||
|
|
63
|
+
method.device?.mainId === connectId ||
|
|
64
|
+
method.device?.getConnectId() === connectId
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
// 取消与指定connectId相关的所有请求
|
|
60
69
|
public abortRequestsByConnectId(connectId: string) {
|
|
61
70
|
let count = 0;
|
|
62
71
|
this.requestQueue.forEach((request, _) => {
|
|
63
|
-
if (request.abortController && request
|
|
72
|
+
if (request.abortController && this.isRequestForConnectId(request, connectId)) {
|
|
64
73
|
request.abortController.abort();
|
|
65
74
|
request.abortController = undefined;
|
|
66
75
|
count++;
|
|
@@ -69,6 +78,12 @@ export default class RequestQueue {
|
|
|
69
78
|
return count;
|
|
70
79
|
}
|
|
71
80
|
|
|
81
|
+
public getRequestTasksIdByConnectId(connectId: string) {
|
|
82
|
+
return Array.from(this.requestQueue.values())
|
|
83
|
+
.filter(request => this.isRequestForConnectId(request, connectId))
|
|
84
|
+
.map(request => request.id);
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
// 取消所有请求
|
|
73
88
|
public abortAllRequests() {
|
|
74
89
|
let count = 0;
|
package/src/core/index.ts
CHANGED
|
@@ -2,7 +2,11 @@ import semver from 'semver';
|
|
|
2
2
|
import EventEmitter from 'events';
|
|
3
3
|
import {
|
|
4
4
|
DeviceSessionPinType,
|
|
5
|
+
type LowlevelTransportSharedPlugin,
|
|
6
|
+
type OneKeyDeviceInfo,
|
|
7
|
+
type ProtocolType,
|
|
5
8
|
TRANSPORT_EVENT,
|
|
9
|
+
type TransportDeviceDisconnectEvent,
|
|
6
10
|
isProtocolV2LinkDisabledError,
|
|
7
11
|
} from '@onekeyfe/hd-transport';
|
|
8
12
|
import {
|
|
@@ -74,11 +78,6 @@ import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } fro
|
|
|
74
78
|
import type { DeviceEvents, InitOptions, RunOptions } from '../device/Device';
|
|
75
79
|
import type { SdkTracingContext } from '../utils/tracing';
|
|
76
80
|
import type { Deferred } from '@onekeyfe/hd-shared';
|
|
77
|
-
import type {
|
|
78
|
-
LowlevelTransportSharedPlugin,
|
|
79
|
-
OneKeyDeviceInfo,
|
|
80
|
-
TransportDeviceDisconnectEvent,
|
|
81
|
-
} from '@onekeyfe/hd-transport';
|
|
82
81
|
import type { BaseMethod } from '../api/BaseMethod';
|
|
83
82
|
|
|
84
83
|
const Log = getLogger(LoggerNames.Core);
|
|
@@ -92,7 +91,7 @@ const preWarmDoneAt = new Map<string, number>();
|
|
|
92
91
|
|
|
93
92
|
export type CoreContext = ReturnType<Core['getCoreContext']>;
|
|
94
93
|
|
|
95
|
-
function
|
|
94
|
+
function resolveDeriveCardano(method: BaseMethod): boolean | undefined {
|
|
96
95
|
if (
|
|
97
96
|
method.name.startsWith('allNetworkGetAddress') &&
|
|
98
97
|
method.payload &&
|
|
@@ -102,15 +101,22 @@ function hasDeriveCardano(method: BaseMethod): boolean {
|
|
|
102
101
|
) {
|
|
103
102
|
return true;
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if (method.name.startsWith('cardano')) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// V1 Initialize only sends derive_cardano on an explicit true.
|
|
108
|
+
// V2 AskPassphrase maps true to [Standard, Cardano] and anything else to [Standard].
|
|
109
|
+
if (method.payload?.deriveCardano === true) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
const parseInitOptions = (method?: BaseMethod): InitOptions => ({
|
|
110
116
|
initSession: method?.payload.initSession,
|
|
111
117
|
passphraseState: method?.payload.useEmptyPassphrase ? undefined : method?.payload.passphraseState,
|
|
112
118
|
deviceId: method?.payload.deviceId,
|
|
113
|
-
deriveCardano: method
|
|
119
|
+
deriveCardano: method ? resolveDeriveCardano(method) : undefined,
|
|
114
120
|
connectProtocol: method?.payload.connectProtocol,
|
|
115
121
|
forceProtocolDetection: method?.payload.forceProtocolDetection,
|
|
116
122
|
protocolV2DeviceInfoTimeoutMs: method?.payload.protocolV2DeviceInfoTimeoutMs,
|
|
@@ -651,7 +657,7 @@ const onCallDevice = async (
|
|
|
651
657
|
method.payload?.passphraseState,
|
|
652
658
|
method.payload?.useEmptyPassphrase,
|
|
653
659
|
method.payload?.skipPassphraseCheck,
|
|
654
|
-
|
|
660
|
+
resolveDeriveCardano(method),
|
|
655
661
|
method.protocolV2UnlockContext?.preflightMainPinSelected
|
|
656
662
|
);
|
|
657
663
|
|
|
@@ -1037,6 +1043,7 @@ async function connectDeviceForBle(
|
|
|
1037
1043
|
!device.commands ||
|
|
1038
1044
|
device.commands.disposed;
|
|
1039
1045
|
if (shouldAcquire) {
|
|
1046
|
+
const connectProtocol = resolveBleConnectProtocol(method);
|
|
1040
1047
|
// The deadline/abort guards are scoped to the desktop electron
|
|
1041
1048
|
// transport: its IPC acquire is the only path with a proven
|
|
1042
1049
|
// never-settling failure mode, while react-native/lowlevel acquire may
|
|
@@ -1048,13 +1055,13 @@ async function connectDeviceForBle(
|
|
|
1048
1055
|
throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
|
|
1049
1056
|
}
|
|
1050
1057
|
if (!useAcquireGuards) {
|
|
1051
|
-
await device.acquire(
|
|
1058
|
+
await device.acquire(connectProtocol, {
|
|
1052
1059
|
forceProtocolDetection: method.payload.forceProtocolDetection,
|
|
1053
1060
|
});
|
|
1054
1061
|
} else {
|
|
1055
1062
|
try {
|
|
1056
1063
|
await raceBleAcquire(
|
|
1057
|
-
device.acquire(
|
|
1064
|
+
device.acquire(connectProtocol, {
|
|
1058
1065
|
forceProtocolDetection: method.payload.forceProtocolDetection,
|
|
1059
1066
|
}),
|
|
1060
1067
|
abortSignal
|
|
@@ -1120,6 +1127,14 @@ async function connectDeviceForBle(
|
|
|
1120
1127
|
}
|
|
1121
1128
|
}
|
|
1122
1129
|
|
|
1130
|
+
export function resolveBleConnectProtocol(method: BaseMethod): ProtocolType | undefined {
|
|
1131
|
+
if (method.payload.connectProtocol === 'V1' || method.payload.connectProtocol === 'V2') {
|
|
1132
|
+
return method.payload.connectProtocol;
|
|
1133
|
+
}
|
|
1134
|
+
const supportedProtocols = method.getSupportedProtocols();
|
|
1135
|
+
return supportedProtocols.length === 1 && supportedProtocols[0] === 'V2' ? 'V2' : undefined;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1123
1138
|
type IPollFn<T> = (time?: number) => T;
|
|
1124
1139
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1125
1140
|
const ensureConnected = async (
|
|
@@ -1318,7 +1333,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1318
1333
|
// cancel callback tasks
|
|
1319
1334
|
requestQueue.cancelCallbackTasks(connectId);
|
|
1320
1335
|
|
|
1321
|
-
const requestIds = requestQueue.
|
|
1336
|
+
const requestIds = requestQueue.getRequestTasksIdByConnectId(connectId);
|
|
1322
1337
|
Log.debug(
|
|
1323
1338
|
`Cancel Api connect requestQueues: length:${requestIds.length} requestIds:${requestIds.join(
|
|
1324
1339
|
','
|
|
@@ -1326,6 +1341,8 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1326
1341
|
);
|
|
1327
1342
|
// Abort before rejecting: rejectRequest releases the task and would make
|
|
1328
1343
|
// its AbortController unreachable to an in-flight method loop.
|
|
1344
|
+
// Match both the requested connectId and a device selected internally by the
|
|
1345
|
+
// method, such as Desktop WebUSB firmwareUpdateV4.
|
|
1329
1346
|
requestQueue.abortRequestsByConnectId(connectId);
|
|
1330
1347
|
const canceledDevices: Device[] = [];
|
|
1331
1348
|
const interruptDevice = (device: Device | undefined, deviceConnectId: string) => {
|
|
@@ -1342,7 +1359,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1342
1359
|
// During ensureConnected the method has a connectId but device is
|
|
1343
1360
|
// assigned only after the poll succeeds. Interrupt the cached BLE
|
|
1344
1361
|
// Device so an in-flight acquire/initialize cannot finish.
|
|
1345
|
-
interruptDevice(task.method?.device,
|
|
1362
|
+
interruptDevice(task.method?.device, connectId);
|
|
1346
1363
|
interruptDevice(deviceCacheMap.get(connectId), connectId);
|
|
1347
1364
|
requestQueue.rejectRequest(
|
|
1348
1365
|
requestId,
|
|
@@ -1357,10 +1374,11 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1357
1374
|
}
|
|
1358
1375
|
} else {
|
|
1359
1376
|
const env = DataManager.getSettings('env');
|
|
1377
|
+
// Abort every method before rejecting its queue task. Non-BLE methods also
|
|
1378
|
+
// use the signal to stop recovery loops after the public promise is rejected.
|
|
1379
|
+
requestQueue.abortAllRequests();
|
|
1360
1380
|
if (DataManager.isBleConnect(env)) {
|
|
1361
1381
|
Log.debug('Cancel Api all _deviceList: ');
|
|
1362
|
-
// Keep method abort signals observable until every active task is rejected.
|
|
1363
|
-
requestQueue.abortAllRequests();
|
|
1364
1382
|
const canceledDevices: Device[] = [];
|
|
1365
1383
|
const interruptDevice = (device?: Device) => {
|
|
1366
1384
|
if (!device || canceledDevices.includes(device)) {
|
|
@@ -1403,8 +1421,10 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1403
1421
|
}
|
|
1404
1422
|
}
|
|
1405
1423
|
|
|
1406
|
-
cleanup();
|
|
1407
|
-
|
|
1424
|
+
cleanup(connectId);
|
|
1425
|
+
if (!connectId || _uiPromises.length === 0) {
|
|
1426
|
+
closePopup();
|
|
1427
|
+
}
|
|
1408
1428
|
};
|
|
1409
1429
|
|
|
1410
1430
|
const checkPassphraseEnableState = (method: BaseMethod, features?: Features) => {
|
|
@@ -1442,9 +1462,16 @@ const shouldCheckPassphraseState = (method: BaseMethod, device: Device) => {
|
|
|
1442
1462
|
return device.hasUsePassphrase();
|
|
1443
1463
|
};
|
|
1444
1464
|
|
|
1445
|
-
const cleanup = () => {
|
|
1446
|
-
const pendingUiPromises =
|
|
1447
|
-
|
|
1465
|
+
const cleanup = (connectId?: string) => {
|
|
1466
|
+
const pendingUiPromises = connectId
|
|
1467
|
+
? _uiPromises.filter(
|
|
1468
|
+
uiPromise =>
|
|
1469
|
+
uiPromise.data?.mainId === connectId || uiPromise.data?.getConnectId() === connectId
|
|
1470
|
+
)
|
|
1471
|
+
: _uiPromises;
|
|
1472
|
+
_uiPromises = connectId
|
|
1473
|
+
? _uiPromises.filter(uiPromise => !pendingUiPromises.includes(uiPromise))
|
|
1474
|
+
: [];
|
|
1448
1475
|
rejectUiPromises(
|
|
1449
1476
|
pendingUiPromises,
|
|
1450
1477
|
ERRORS.TypedError(HardwareErrorCode.ActionCancelled, 'UI request was cancelled')
|