@onekeyfe/hd-core 1.2.0-alpha.134 → 1.2.0-alpha.135
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__/DeviceCommands.test.ts +8 -1
- package/__tests__/device-lifecycle-events.test.ts +95 -2
- package/__tests__/device-settings.test.ts +167 -9
- package/__tests__/device-wallet-session-store.test.ts +21 -79
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +37 -1
- package/__tests__/protocol-v2-unlock-policy.test.ts +59 -0
- package/__tests__/protocol-v2.test.ts +86 -21
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceVerify.d.ts +1 -0
- package/dist/api/device/DeviceVerify.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +2 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +296 -247
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +25 -68
- package/src/api/device/DeviceChangePin.ts +4 -1
- package/src/api/device/DeviceSettings.ts +2 -16
- package/src/api/device/DeviceVerify.ts +9 -0
- package/src/api/device/DeviceWipe.ts +4 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +11 -0
- package/src/core/index.ts +67 -25
- package/src/device/Device.ts +45 -42
- package/src/device/DeviceCommands.ts +15 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-core",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.135",
|
|
4
4
|
"description": "Core processes and APIs for communicating with OneKey hardware devices.",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
29
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.135",
|
|
29
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.135",
|
|
30
30
|
"axios": "1.15.2",
|
|
31
31
|
"bignumber.js": "^9.0.2",
|
|
32
32
|
"buffer": "^6.0.3",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"@types/w3c-web-usb": "^1.0.10",
|
|
47
47
|
"@types/web-bluetooth": "^0.0.21"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "321e900381de2c1a9c583e90e8abfc2f8dab8266"
|
|
50
50
|
}
|
|
@@ -2,6 +2,7 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
|
|
|
2
2
|
import JSZip from 'jszip';
|
|
3
3
|
import {
|
|
4
4
|
DeviceRebootType,
|
|
5
|
+
DeviceSessionPinType,
|
|
5
6
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
6
7
|
PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
|
|
7
8
|
PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
|
|
@@ -90,9 +91,8 @@ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
|
|
|
90
91
|
const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
|
|
91
92
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
92
93
|
const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
|
|
93
|
-
const PROTOCOL_V2_INSTALL_TIMEOUT =
|
|
94
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
94
95
|
const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
|
|
95
|
-
const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
|
|
96
96
|
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
97
97
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
98
98
|
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
@@ -106,6 +106,8 @@ const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
|
106
106
|
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
|
|
107
107
|
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
108
108
|
const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
|
|
109
|
+
const PROTOCOL_V2_INSTALL_FAILED_CODE = 'FirmwareInstallFailed';
|
|
110
|
+
const PROTOCOL_V2_INSTALL_TIMEOUT_CODE = 'FirmwareInstallTimeout';
|
|
109
111
|
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
110
112
|
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
111
113
|
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
@@ -391,7 +393,8 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
|
|
|
391
393
|
|
|
392
394
|
const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
|
|
393
395
|
error instanceof HardwareError &&
|
|
394
|
-
(error.errorCode === HardwareErrorCode.
|
|
396
|
+
(error.errorCode === HardwareErrorCode.ActionCancelled ||
|
|
397
|
+
error.errorCode === HardwareErrorCode.FirmwareError ||
|
|
395
398
|
error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
|
|
396
399
|
error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
|
|
397
400
|
|
|
@@ -599,6 +602,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
599
602
|
this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
|
|
600
603
|
this.requireDeviceMode = [];
|
|
601
604
|
this.unlockPolicy = 'unlock-before-run';
|
|
605
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
606
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
607
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
602
608
|
this.useDevicePassphraseState = false;
|
|
603
609
|
this.skipForceUpdateCheck = true;
|
|
604
610
|
|
|
@@ -1623,9 +1629,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
1623
1629
|
Math.floor(statusVersion / 0x100) % 0x100
|
|
1624
1630
|
}.${statusVersion % 0x100}`;
|
|
1625
1631
|
if (!observedVersion) {
|
|
1626
|
-
|
|
1632
|
+
const isUnobservableApplicationSlot =
|
|
1633
|
+
applicationTargets.length > 1 && (target === 'app_v1' || target === 'app_v2');
|
|
1634
|
+
if (this.protocolV2FinalStatusVerified || isUnobservableApplicationSlot) {
|
|
1627
1635
|
Log.warn(
|
|
1628
|
-
`Protocol V2 target ${target} has no observable final version;
|
|
1636
|
+
`Protocol V2 target ${target} has no observable final version; install completion is authoritative`
|
|
1629
1637
|
);
|
|
1630
1638
|
} else {
|
|
1631
1639
|
throw ERRORS.TypedError(
|
|
@@ -2499,14 +2507,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2499
2507
|
path: failedTarget.path,
|
|
2500
2508
|
});
|
|
2501
2509
|
Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
|
|
2502
|
-
throw ERRORS.TypedError(
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
failedTarget.status ?? 'unknown'
|
|
2506
|
-
} payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
|
|
2507
|
-
failedTarget.path ?? 'unknown'
|
|
2508
|
-
}`
|
|
2509
|
-
);
|
|
2510
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
|
|
2511
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_FAILED_CODE,
|
|
2512
|
+
});
|
|
2510
2513
|
}
|
|
2511
2514
|
|
|
2512
2515
|
const matchingTargets = statusTargets.filter(target =>
|
|
@@ -2520,13 +2523,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2520
2523
|
seenTargetIds.has(targetId) ||
|
|
2521
2524
|
(target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
|
|
2522
2525
|
) {
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2528
|
-
}
|
|
2529
|
-
);
|
|
2526
|
+
Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
|
|
2527
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
|
|
2528
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
|
|
2529
|
+
});
|
|
2530
2530
|
}
|
|
2531
2531
|
seenTargetIds.add(targetId);
|
|
2532
2532
|
}
|
|
@@ -2651,15 +2651,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2651
2651
|
// Reconnecting here probes DeviceInfo, which loaders reject while installation is active.
|
|
2652
2652
|
let shouldReconnect = false;
|
|
2653
2653
|
let deviceInfo: ProtocolV2DeviceInfo | undefined;
|
|
2654
|
-
let missingTargetStatusSince: number | undefined;
|
|
2655
|
-
let missingTargetStatusKey: string | undefined;
|
|
2656
|
-
let normalModeWithoutInstallEvidenceSince: number | undefined;
|
|
2657
2654
|
let installEvidenceObserved = false;
|
|
2658
2655
|
let currentInstallStatusObserved = false;
|
|
2659
|
-
const resetMissingTargetStatusGrace = () => {
|
|
2660
|
-
missingTargetStatusSince = undefined;
|
|
2661
|
-
missingTargetStatusKey = undefined;
|
|
2662
|
-
};
|
|
2663
2656
|
|
|
2664
2657
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
2665
2658
|
// A transport release caused by an explicit workflow cancellation must not
|
|
@@ -2687,7 +2680,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2687
2680
|
);
|
|
2688
2681
|
if (statusResponse.type === 'Success') {
|
|
2689
2682
|
installEvidenceObserved = true;
|
|
2690
|
-
resetMissingTargetStatusGrace();
|
|
2691
2683
|
lastError = new Error(
|
|
2692
2684
|
'Protocol V2 firmware install acknowledged; waiting for target status'
|
|
2693
2685
|
);
|
|
@@ -2716,7 +2708,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2716
2708
|
(!requireCurrentInstallStatus || currentInstallStatusObserved)
|
|
2717
2709
|
) {
|
|
2718
2710
|
installEvidenceObserved = true;
|
|
2719
|
-
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2720
2711
|
}
|
|
2721
2712
|
const matchingStatusTargets = statusTargets.filter(target => {
|
|
2722
2713
|
const targetId = normalizeProtocolV2TargetId(target.target_id);
|
|
@@ -2758,7 +2749,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2758
2749
|
}
|
|
2759
2750
|
|
|
2760
2751
|
if (statusTargets.length === 0) {
|
|
2761
|
-
resetMissingTargetStatusGrace();
|
|
2762
2752
|
if (statusResponse.type !== 'Success') {
|
|
2763
2753
|
lastError = new Error(
|
|
2764
2754
|
'Protocol V2 firmware update is waiting for user confirmation or target status'
|
|
@@ -2770,30 +2760,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2770
2760
|
expectedTargetIds
|
|
2771
2761
|
);
|
|
2772
2762
|
if (missingTargetIds.length > 0) {
|
|
2773
|
-
const now = Date.now();
|
|
2774
2763
|
const missingKey = missingTargetIds.join(',');
|
|
2775
|
-
if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
|
|
2776
|
-
missingTargetStatusSince = now;
|
|
2777
|
-
missingTargetStatusKey = missingKey;
|
|
2778
|
-
} else if (
|
|
2779
|
-
now - missingTargetStatusSince >=
|
|
2780
|
-
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2781
|
-
) {
|
|
2782
|
-
const reportedTargetIds = statusTargets
|
|
2783
|
-
.map(target => normalizeProtocolV2TargetId(target.target_id))
|
|
2784
|
-
.filter((targetId): targetId is number => targetId !== undefined);
|
|
2785
|
-
throw ERRORS.TypedError(
|
|
2786
|
-
HardwareErrorCode.FirmwareError,
|
|
2787
|
-
`Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
|
|
2788
|
-
','
|
|
2789
|
-
)}`
|
|
2790
|
-
);
|
|
2791
|
-
}
|
|
2792
2764
|
lastError = new Error(
|
|
2793
2765
|
`Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
|
|
2794
2766
|
);
|
|
2795
2767
|
} else {
|
|
2796
|
-
resetMissingTargetStatusGrace();
|
|
2797
2768
|
lastError = new Error('Protocol V2 firmware targets are still installing');
|
|
2798
2769
|
}
|
|
2799
2770
|
}
|
|
@@ -2801,9 +2772,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2801
2772
|
if (isProtocolV2TerminalInstallStatusError(error)) {
|
|
2802
2773
|
throw error;
|
|
2803
2774
|
}
|
|
2804
|
-
// Only consecutive full status dumps can prove that a record is absent.
|
|
2805
|
-
// A reboot or transport interruption starts a fresh grace window.
|
|
2806
|
-
resetMissingTargetStatusGrace();
|
|
2807
2775
|
// App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
|
|
2808
2776
|
// missing endpoint as completion only after the runtime probe confirms App mode.
|
|
2809
2777
|
if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
|
|
@@ -2826,22 +2794,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2826
2794
|
return;
|
|
2827
2795
|
}
|
|
2828
2796
|
if (isNormalMode) {
|
|
2829
|
-
const now = Date.now();
|
|
2830
|
-
normalModeWithoutInstallEvidenceSince ??= now;
|
|
2831
|
-
if (
|
|
2832
|
-
now - normalModeWithoutInstallEvidenceSince >=
|
|
2833
|
-
PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
|
|
2834
|
-
) {
|
|
2835
|
-
throw ERRORS.TypedError(
|
|
2836
|
-
HardwareErrorCode.FirmwareError,
|
|
2837
|
-
'Protocol V2 device returned to normal mode without install ACK, target status, or version change'
|
|
2838
|
-
);
|
|
2839
|
-
}
|
|
2840
2797
|
lastError = new Error(
|
|
2841
2798
|
'Protocol V2 device is in normal mode but installation is not yet confirmed'
|
|
2842
2799
|
);
|
|
2843
2800
|
} else {
|
|
2844
|
-
normalModeWithoutInstallEvidenceSince = undefined;
|
|
2845
2801
|
lastError = new Error(
|
|
2846
2802
|
'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
|
|
2847
2803
|
);
|
|
@@ -2872,18 +2828,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
2872
2828
|
}
|
|
2873
2829
|
shouldReconnect = true;
|
|
2874
2830
|
deviceInfo = undefined;
|
|
2875
|
-
resetMissingTargetStatusGrace();
|
|
2876
2831
|
Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
|
|
2877
2832
|
}
|
|
2878
2833
|
await wait(1000);
|
|
2879
2834
|
}
|
|
2880
2835
|
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
`Protocol V2 firmware update status not complete within ${
|
|
2836
|
+
Log.error(
|
|
2837
|
+
`[FirmwareUpdateV4] install timed out after ${
|
|
2884
2838
|
PROTOCOL_V2_INSTALL_TIMEOUT / 1000
|
|
2885
2839
|
}s: ${this.normalizeErrorMessage(lastError)}`
|
|
2886
2840
|
);
|
|
2841
|
+
throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
|
|
2842
|
+
firmwareUpdateCode: PROTOCOL_V2_INSTALL_TIMEOUT_CODE,
|
|
2843
|
+
});
|
|
2887
2844
|
}
|
|
2888
2845
|
|
|
2889
2846
|
private async exitProtocolV2BootloaderToNormal() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
1
|
+
import { DeviceSessionPinType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
2
2
|
|
|
3
3
|
import { BaseMethod } from '../BaseMethod';
|
|
4
4
|
import { validateParams } from '../helpers/paramsValidator';
|
|
@@ -13,6 +13,9 @@ export default class DeviceChangePin extends BaseMethod<ChangePin> {
|
|
|
13
13
|
|
|
14
14
|
init() {
|
|
15
15
|
this.unlockPolicy = 'unlock-before-run';
|
|
16
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
17
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
18
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
16
19
|
this.useDevicePassphraseState = false;
|
|
17
20
|
|
|
18
21
|
// check payload
|
|
@@ -168,8 +168,7 @@ export default class DeviceSettings extends BaseMethod<ApplySettings> {
|
|
|
168
168
|
async run() {
|
|
169
169
|
try {
|
|
170
170
|
if (this.device.isProtocolV2()) {
|
|
171
|
-
const refreshStatusAndSettings = () =>
|
|
172
|
-
this.device.getDeviceState({ refreshSections: ['status', 'settings'] });
|
|
171
|
+
const refreshStatusAndSettings = () => this.device.refreshProtocolV2SettingsAfterMutation();
|
|
173
172
|
assertSettingsSupported(this.payload, DEVICE_SETTINGS_V1_ONLY_FIELDS, 'Protocol V2');
|
|
174
173
|
const capabilities = getDeviceSettingsCapabilities(
|
|
175
174
|
this.device.getCurrentDeviceType(),
|
|
@@ -201,20 +200,7 @@ export default class DeviceSettings extends BaseMethod<ApplySettings> {
|
|
|
201
200
|
const res = await this.device.commands.typedCall('DeviceSettingsPageShow', 'Success', {
|
|
202
201
|
page: DeviceSettingsPage.DevicePassphrase,
|
|
203
202
|
});
|
|
204
|
-
|
|
205
|
-
const lockedAfterDisabling =
|
|
206
|
-
requestedPassphrase === false &&
|
|
207
|
-
current.status.unlocked === true &&
|
|
208
|
-
updated.status.unlocked === false;
|
|
209
|
-
if (
|
|
210
|
-
updated.status.passphraseProtection !== requestedPassphrase &&
|
|
211
|
-
!lockedAfterDisabling
|
|
212
|
-
) {
|
|
213
|
-
throw TypedError(
|
|
214
|
-
HardwareErrorCode.RuntimeError,
|
|
215
|
-
'Protocol V2 passphrase setting did not reach the requested value.'
|
|
216
|
-
);
|
|
217
|
-
}
|
|
203
|
+
await refreshStatusAndSettings();
|
|
218
204
|
return res.message;
|
|
219
205
|
}
|
|
220
206
|
if (requestedAirgap !== undefined) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sha256 } from '@noble/hashes/sha256';
|
|
2
2
|
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
+
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
3
4
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
4
5
|
|
|
5
6
|
import { formatAnyHex } from '../helpers/hexUtils';
|
|
@@ -11,7 +12,15 @@ import type { BixinVerifyDeviceRequest } from '@onekeyfe/hd-transport';
|
|
|
11
12
|
import type { DeviceVerifySignature } from '../../types';
|
|
12
13
|
|
|
13
14
|
export default class DeviceVerify extends BaseMethod<BixinVerifyDeviceRequest> {
|
|
15
|
+
getSupportedProtocols() {
|
|
16
|
+
return ['V1', 'V2'] as const;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
init() {
|
|
20
|
+
this.unlockPolicy = 'unlock-before-run';
|
|
21
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
22
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
23
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
15
24
|
this.useDevicePassphraseState = false;
|
|
16
25
|
|
|
17
26
|
// check payload
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
1
|
+
import { DeviceSessionPinType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
|
|
2
2
|
|
|
3
3
|
import { BaseMethod } from '../BaseMethod';
|
|
4
4
|
|
|
@@ -11,6 +11,9 @@ export default class DeviceWipe extends BaseMethod<WipeDevice> {
|
|
|
11
11
|
|
|
12
12
|
init() {
|
|
13
13
|
this.unlockPolicy = 'unlock-before-run';
|
|
14
|
+
// Protocol V2 device-management actions are not wallet-scoped, so either
|
|
15
|
+
// the main PIN or an Attach PIN may authorize them.
|
|
16
|
+
this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
|
|
14
17
|
this.useDevicePassphraseState = false;
|
|
15
18
|
this.protocolV2UiInteraction = {
|
|
16
19
|
request: 'button',
|
|
@@ -8,6 +8,7 @@ import { invalidParameter } from '../helpers/filesystemValidation';
|
|
|
8
8
|
import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
|
|
9
9
|
import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
|
|
10
10
|
import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
|
|
11
|
+
import { LoggerNames, getLogger } from '../../utils';
|
|
11
12
|
import {
|
|
12
13
|
PRO2_WALLPAPER_HEIGHT,
|
|
13
14
|
PRO2_WALLPAPER_WIDTH,
|
|
@@ -33,6 +34,7 @@ const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
|
|
|
33
34
|
const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
|
|
34
35
|
const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
|
|
35
36
|
const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
|
|
37
|
+
const Log = getLogger(LoggerNames.Method);
|
|
36
38
|
|
|
37
39
|
function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
|
|
38
40
|
if (fileName !== undefined && (!fileName || !SAFE_FILE_NAME.test(fileName))) {
|
|
@@ -143,6 +145,15 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
143
145
|
const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
|
|
144
146
|
settings: { wallpaper_path: this.path },
|
|
145
147
|
});
|
|
148
|
+
try {
|
|
149
|
+
await this.device.refreshProtocolV2SettingsAfterMutation();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// The wallpaper is already applied. A transient read-back failure must not
|
|
152
|
+
// make callers retry the completed file upload.
|
|
153
|
+
Log.warn('Protocol V2 wallpaper settings refresh failed after apply', {
|
|
154
|
+
error: error instanceof Error ? error.message : String(error),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
146
157
|
return {
|
|
147
158
|
path: this.path,
|
|
148
159
|
size: encoded.data.byteLength,
|
package/src/core/index.ts
CHANGED
|
@@ -83,6 +83,7 @@ import type { BaseMethod } from '../api/BaseMethod';
|
|
|
83
83
|
|
|
84
84
|
const Log = getLogger(LoggerNames.Core);
|
|
85
85
|
const PRE_INITIALIZE_TTL_MS = 60 * 1000;
|
|
86
|
+
const PRE_PENDING_CALL_TIMEOUT_MS = 15 * 1000;
|
|
86
87
|
|
|
87
88
|
// Dedup/coalesce state for "pre-warm signal" methods (isPreWarmSignal),
|
|
88
89
|
// keyed by getPreWarmKey(): coalesce in-flight, skip if warmed within TTL.
|
|
@@ -296,26 +297,35 @@ const handlePreWarmSignal = async (
|
|
|
296
297
|
}
|
|
297
298
|
};
|
|
298
299
|
|
|
299
|
-
const waitWithTimeout = async (promise: Promise<any>, timeout: number) => {
|
|
300
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
301
|
-
setTimeout(() => reject(new Error('Request timeout')), timeout);
|
|
302
|
-
});
|
|
303
|
-
return Promise.race([promise, timeoutPromise]);
|
|
304
|
-
};
|
|
305
|
-
|
|
306
300
|
const waitForPendingPromise = async (
|
|
307
|
-
|
|
308
|
-
|
|
301
|
+
connectId: string,
|
|
302
|
+
getPrePendingCallPromise: (connectId: string) => Promise<void> | undefined,
|
|
303
|
+
removePrePendingCallPromise?: (connectId: string, promise: Promise<void>) => void
|
|
309
304
|
) => {
|
|
310
|
-
const pendingPromise = getPrePendingCallPromise();
|
|
305
|
+
const pendingPromise = getPrePendingCallPromise(connectId);
|
|
311
306
|
if (pendingPromise) {
|
|
312
307
|
Log.debug('pre pending call promise before call method, wait for it');
|
|
308
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
309
|
+
let timedOut = false;
|
|
313
310
|
try {
|
|
314
|
-
await
|
|
311
|
+
await Promise.race([
|
|
312
|
+
pendingPromise,
|
|
313
|
+
new Promise<void>(resolve => {
|
|
314
|
+
timer = setTimeout(() => {
|
|
315
|
+
timedOut = true;
|
|
316
|
+
resolve();
|
|
317
|
+
}, PRE_PENDING_CALL_TIMEOUT_MS);
|
|
318
|
+
}),
|
|
319
|
+
]);
|
|
315
320
|
} catch (error) {
|
|
316
|
-
//
|
|
321
|
+
// Cancellation is best-effort; the transport teardown owns recovery.
|
|
322
|
+
} finally {
|
|
323
|
+
if (timer) clearTimeout(timer);
|
|
324
|
+
removePrePendingCallPromise?.(connectId, pendingPromise);
|
|
325
|
+
}
|
|
326
|
+
if (timedOut) {
|
|
327
|
+
Log.warn('pre pending call promise timed out before call method', { connectId });
|
|
317
328
|
}
|
|
318
|
-
removePrePendingCallPromise?.(pendingPromise);
|
|
319
329
|
Log.debug('pre pending call promise before call method done');
|
|
320
330
|
}
|
|
321
331
|
};
|
|
@@ -327,7 +337,7 @@ const onCallDevice = async (
|
|
|
327
337
|
): Promise<any> => {
|
|
328
338
|
let messageResponse: any;
|
|
329
339
|
|
|
330
|
-
const { requestQueue, getPrePendingCallPromise,
|
|
340
|
+
const { requestQueue, getPrePendingCallPromise, removePrePendingCallPromise } = context;
|
|
331
341
|
|
|
332
342
|
updateMethodRequestContext(method, { status: 'running' });
|
|
333
343
|
|
|
@@ -355,7 +365,11 @@ const onCallDevice = async (
|
|
|
355
365
|
await context.waitForCallbackTasks(method.connectId);
|
|
356
366
|
}
|
|
357
367
|
|
|
358
|
-
await waitForPendingPromise(
|
|
368
|
+
await waitForPendingPromise(
|
|
369
|
+
method.connectId ?? '',
|
|
370
|
+
getPrePendingCallPromise,
|
|
371
|
+
removePrePendingCallPromise
|
|
372
|
+
);
|
|
359
373
|
|
|
360
374
|
const task = requestQueue.createTask(method);
|
|
361
375
|
|
|
@@ -454,7 +468,11 @@ const onCallDevice = async (
|
|
|
454
468
|
await context.waitForCallbackTasks(method.connectId, preWarmCallbackTask);
|
|
455
469
|
}
|
|
456
470
|
|
|
457
|
-
await waitForPendingPromise(
|
|
471
|
+
await waitForPendingPromise(
|
|
472
|
+
method.connectId ?? '',
|
|
473
|
+
getPrePendingCallPromise,
|
|
474
|
+
removePrePendingCallPromise
|
|
475
|
+
);
|
|
458
476
|
|
|
459
477
|
const inner = async (): Promise<void> => {
|
|
460
478
|
// Protocol is established from an active device response during acquire/initialize.
|
|
@@ -906,6 +924,16 @@ function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown)
|
|
|
906
924
|
);
|
|
907
925
|
}
|
|
908
926
|
|
|
927
|
+
export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unknown) {
|
|
928
|
+
const typedError = error as { errorCode?: unknown; message?: unknown };
|
|
929
|
+
return (
|
|
930
|
+
method.payload.connectProtocol === 'V2' &&
|
|
931
|
+
typedError?.errorCode === HardwareErrorCode.RuntimeError &&
|
|
932
|
+
typeof typedError.message === 'string' &&
|
|
933
|
+
typedError.message.includes('Device protocol has not been detected')
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
909
937
|
/**
|
|
910
938
|
* If the Bluetooth connection times out, retry up to 6 times
|
|
911
939
|
* @param retryCount - Current retry count (default 0)
|
|
@@ -943,12 +971,13 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
943
971
|
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
944
972
|
}
|
|
945
973
|
} catch (err) {
|
|
974
|
+
const requiresColdReconnect = isMissingDetectedProtocolV2Error(method, err);
|
|
946
975
|
// Device.run()'s REQUIRE_DISCONNECT handling never sees acquire/initialize
|
|
947
976
|
// failures, so with keep-alive a wedged link would be reused by every retry
|
|
948
977
|
// (field case: Initialize timing out at 25s per attempt, forever). Drop it
|
|
949
978
|
// here so the next retry cold-connects.
|
|
950
979
|
if (
|
|
951
|
-
ERROR_CODES_REQUIRE_DISCONNECT.includes(err.errorCode) &&
|
|
980
|
+
(ERROR_CODES_REQUIRE_DISCONNECT.includes(err.errorCode) || requiresColdReconnect) &&
|
|
952
981
|
device.mainId &&
|
|
953
982
|
device.deviceConnector
|
|
954
983
|
) {
|
|
@@ -961,11 +990,12 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
961
990
|
if (
|
|
962
991
|
(err.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
963
992
|
err.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
964
|
-
isRetryableBleProtocolV2ProbeError(method, err)
|
|
993
|
+
isRetryableBleProtocolV2ProbeError(method, err) ||
|
|
994
|
+
requiresColdReconnect) &&
|
|
965
995
|
retryCount < 6
|
|
966
996
|
) {
|
|
967
997
|
const nextRetry = retryCount + 1;
|
|
968
|
-
Log.debug(`Bluetooth
|
|
998
|
+
Log.debug(`Bluetooth connection will retry, retry count: ${nextRetry}`);
|
|
969
999
|
await wait(3000);
|
|
970
1000
|
await connectDeviceForBle(method, device, nextRetry);
|
|
971
1001
|
} else {
|
|
@@ -1177,7 +1207,10 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1177
1207
|
if (task && task.method?.device) {
|
|
1178
1208
|
if (!canceledDevices.includes(task.method.device)) {
|
|
1179
1209
|
const { device } = task.method;
|
|
1180
|
-
setPrePendingCallPromise(
|
|
1210
|
+
setPrePendingCallPromise(
|
|
1211
|
+
task.method.connectId ?? connectId,
|
|
1212
|
+
device.interruptionFromUser()
|
|
1213
|
+
);
|
|
1181
1214
|
canceledDevices.push(device);
|
|
1182
1215
|
}
|
|
1183
1216
|
requestQueue.rejectRequest(
|
|
@@ -1531,7 +1564,7 @@ export default class Core extends EventEmitter {
|
|
|
1531
1564
|
private disposePromise?: Promise<void>;
|
|
1532
1565
|
|
|
1533
1566
|
// background task
|
|
1534
|
-
private
|
|
1567
|
+
private prePendingCallPromises = new Map<string, Promise<void>>();
|
|
1535
1568
|
|
|
1536
1569
|
private methodSynchronize = getSynchronize();
|
|
1537
1570
|
|
|
@@ -1548,9 +1581,18 @@ export default class Core extends EventEmitter {
|
|
|
1548
1581
|
tracingContext: this.tracingContext,
|
|
1549
1582
|
requestQueue: this.requestQueue,
|
|
1550
1583
|
methodSynchronize: this.methodSynchronize,
|
|
1551
|
-
getPrePendingCallPromise: () => this.
|
|
1552
|
-
setPrePendingCallPromise: (
|
|
1553
|
-
|
|
1584
|
+
getPrePendingCallPromise: (connectId: string) => this.prePendingCallPromises.get(connectId),
|
|
1585
|
+
setPrePendingCallPromise: (connectId: string, promise?: Promise<void>) => {
|
|
1586
|
+
if (!promise) {
|
|
1587
|
+
this.prePendingCallPromises.delete(connectId);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
this.prePendingCallPromises.set(connectId, promise);
|
|
1591
|
+
},
|
|
1592
|
+
removePrePendingCallPromise: (connectId: string, promise: Promise<void>) => {
|
|
1593
|
+
if (this.prePendingCallPromises.get(connectId) === promise) {
|
|
1594
|
+
this.prePendingCallPromises.delete(connectId);
|
|
1595
|
+
}
|
|
1554
1596
|
},
|
|
1555
1597
|
// callback 任务管理
|
|
1556
1598
|
registerCallbackTask: (connectId: string, callbackPromise: Deferred<any>) => {
|
|
@@ -1665,7 +1707,7 @@ export default class Core extends EventEmitter {
|
|
|
1665
1707
|
preWarmInflight.clear();
|
|
1666
1708
|
preWarmDoneAt.clear();
|
|
1667
1709
|
preConnectCache = { passphraseState: undefined };
|
|
1668
|
-
this.
|
|
1710
|
+
this.prePendingCallPromises.clear();
|
|
1669
1711
|
this.removeAllListeners();
|
|
1670
1712
|
cleanupSdkInstance(this.sdkInstanceId);
|
|
1671
1713
|
if (_core === this) _core = undefined;
|