@onekeyfe/hd-core 1.2.0-alpha.19 → 1.2.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +75 -0
- package/__tests__/DeviceCommands.test.ts +143 -7
- package/__tests__/RequestQueue.test.ts +106 -0
- package/__tests__/UploadPortfolio.test.ts +46 -0
- package/__tests__/core-dispose.test.ts +34 -0
- package/__tests__/core-error-output.test.ts +39 -0
- package/__tests__/device-lifecycle-events.test.ts +117 -0
- package/__tests__/device-pool-state.test.ts +20 -1
- package/__tests__/device-settings.test.ts +184 -6
- package/__tests__/device-state-events.test.ts +1 -2
- package/__tests__/device-state-mapper.test.ts +33 -26
- package/__tests__/device-state-store.test.ts +1 -34
- package/__tests__/device-utils.test.ts +106 -0
- package/__tests__/device-wallet-session-store.test.ts +65 -29
- package/__tests__/deviceSettings.test.ts +21 -12
- package/__tests__/evmLedgerLegacySafety.test.ts +2 -2
- package/__tests__/evmSignTypedData.test.ts +80 -0
- package/__tests__/filesystemValidation.test.ts +53 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +402 -0
- package/__tests__/get-device-state.test.ts +77 -41
- package/__tests__/logBlockEvent.test.ts +24 -11
- package/__tests__/networkUtils.test.ts +386 -0
- package/__tests__/open-wallet-session-error-response.test.ts +43 -0
- package/__tests__/open-wallet-session.test.ts +559 -0
- package/__tests__/protocol-v2.test.ts +756 -452
- package/__tests__/public-device-state-api.test.ts +113 -13
- package/__tests__/public-pro2-api-boundary.test.ts +91 -0
- package/__tests__/search-devices.test.ts +43 -0
- package/dist/api/BaseMethod.d.ts +3 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckBootloaderRelease.d.ts.map +1 -1
- package/dist/api/CheckFirmwareRelease.d.ts.map +1 -1
- package/dist/api/ClearSessionCache.d.ts.map +1 -1
- package/dist/api/DirList.d.ts.map +1 -1
- package/dist/api/FileRead.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +2 -2
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +3 -2
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts +14 -0
- package/dist/api/OpenWalletSession.d.ts.map +1 -0
- package/dist/api/PathInfo.d.ts.map +1 -1
- package/dist/api/PromptWebDeviceAccess.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +3 -0
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/UploadPortfolio.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
- package/dist/api/device/DeviceFullyUploadResource.d.ts +1 -2
- package/dist/api/device/DeviceFullyUploadResource.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +7 -3
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/helpers/filesystemValidation.d.ts +3 -0
- package/dist/api/helpers/filesystemValidation.d.ts.map +1 -1
- package/dist/api/helpers/paramsValidator.d.ts +1 -0
- package/dist/api/helpers/paramsValidator.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +7 -11
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -1
- package/dist/api/utils.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +17 -1
- package/dist/core/RequestQueue.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 +6 -5
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +5 -5
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +1 -0
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts +2 -2
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts +1 -2
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/device/DeviceStateStore.d.ts +1 -3
- package/dist/device/DeviceStateStore.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +6 -1
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
- package/dist/device/cloneDeviceState.d.ts +2 -0
- package/dist/device/cloneDeviceState.d.ts.map +1 -0
- package/dist/deviceProfile/buildDeviceFeatures.d.ts +3 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +608 -75
- package/dist/index.js +2287 -1706
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/deviceSession.d.ts +7 -0
- package/dist/protocols/protocol-v2/deviceSession.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/features.d.ts +11 -4
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +16 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +8 -3
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -2
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceSettings.d.ts +1 -0
- package/dist/types/api/deviceSettings.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +1 -1
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/getFeatures.d.ts.map +1 -1
- package/dist/types/api/getOnekeyFeatures.d.ts.map +1 -1
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +13 -15
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +41 -0
- package/dist/types/api/openWalletSession.d.ts.map +1 -0
- package/dist/types/api/protocolV2.d.ts +6 -11
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -9
- package/dist/types/device.d.ts.map +1 -1
- package/dist/utils/assets.d.ts +2 -1
- package/dist/utils/assets.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts +13 -0
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -0
- package/dist/utils/deviceFeaturesUtils.d.ts +11 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts +9 -8
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/deviceSettings.d.ts +2 -0
- package/dist/utils/deviceSettings.d.ts.map +1 -1
- package/dist/utils/deviceVersionUtils.d.ts +6 -5
- package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/networkUtils.d.ts +9 -1
- package/dist/utils/networkUtils.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/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +17 -10
- package/src/api/CheckBootloaderRelease.ts +1 -2
- package/src/api/CheckFirmwareRelease.ts +1 -2
- package/src/api/ClearSessionCache.ts +0 -4
- package/src/api/DirList.ts +5 -3
- package/src/api/DirMake.ts +3 -3
- package/src/api/DirRemove.ts +3 -3
- package/src/api/FileDelete.ts +3 -3
- package/src/api/FileRead.ts +9 -6
- package/src/api/FileWrite.ts +3 -3
- package/src/api/FirmwareUpdate.ts +4 -4
- package/src/api/FirmwareUpdateV2.ts +133 -45
- package/src/api/FirmwareUpdateV3.ts +3 -5
- package/src/api/FirmwareUpdateV4.ts +192 -154
- package/src/api/GetPassphraseState.ts +10 -16
- package/src/api/OpenWalletSession.ts +221 -0
- package/src/api/PathInfo.ts +5 -3
- package/src/api/PromptWebDeviceAccess.ts +2 -3
- package/src/api/SearchDevices.ts +6 -2
- package/src/api/UploadPortfolio.ts +12 -3
- package/src/api/alephium/AlephiumSignTransaction.ts +2 -2
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +4 -8
- package/src/api/benfen/BenfenGetAddress.ts +1 -1
- package/src/api/btc/BTCGetPublicKey.ts +1 -1
- package/src/api/device/DeviceChangePin.ts +1 -0
- package/src/api/device/DeviceFullyUploadResource.ts +4 -5
- package/src/api/device/DeviceSettings.ts +93 -0
- package/src/api/device/DeviceUpdateBootloader.ts +1 -2
- package/src/api/device/DeviceWipe.ts +1 -0
- package/src/api/evm/EVMSignTypedData.ts +3 -5
- package/src/api/evm/legacyV1/getAddress.ts +2 -2
- package/src/api/evm/legacyV1/getPublicKey.ts +2 -2
- package/src/api/evm/legacyV1/signMessage.ts +2 -2
- package/src/api/evm/legacyV1/signTypedData.ts +2 -2
- package/src/api/evm/legacyV1/signTypedHash.ts +2 -2
- package/src/api/evm/legacyV1/verifyMessage.ts +2 -2
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +3 -3
- package/src/api/firmware/getBinary.ts +8 -3
- package/src/api/helpers/batchGetPublickeys.ts +2 -2
- package/src/api/helpers/filesystemValidation.ts +61 -0
- package/src/api/helpers/paramsValidator.ts +1 -1
- package/src/api/helpers/protocolV2FileWrite.ts +3 -2
- package/src/api/index.ts +9 -12
- package/src/api/nervos/NervosSignTransaction.ts +2 -2
- package/src/api/nexa/NexaSignTransaction.ts +1 -1
- package/src/api/protocol-v2/DeviceFactoryInfoGet.ts +1 -1
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +4 -4
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +6 -5
- package/src/api/protocol-v2/DeviceInfoGet.ts +4 -4
- package/src/api/protocol-v2/DeviceReboot.ts +1 -1
- package/src/api/protocol-v2/DeviceSettingsPageShow.ts +11 -8
- package/src/api/protocol-v2/DeviceSettingsSet.ts +4 -11
- package/src/api/protocol-v2/FilesystemFormat.ts +1 -1
- package/src/api/protocol-v2/FilesystemPermissionFix.ts +1 -1
- package/src/api/protocol-v2/Ping.ts +1 -1
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +1 -1
- package/src/api/utils.ts +1 -3
- package/src/core/RequestQueue.ts +107 -6
- package/src/core/index.ts +91 -46
- package/src/data/messages/messages-protocol-v2.json +25 -79
- package/src/data-manager/DataManager.ts +6 -6
- package/src/device/Device.ts +186 -102
- package/src/device/DeviceCommands.ts +51 -21
- package/src/device/DeviceFeaturesState.ts +4 -4
- package/src/device/DevicePool.ts +17 -3
- package/src/device/DeviceStateMapper.ts +13 -29
- package/src/device/DeviceStateProjector.ts +11 -8
- package/src/device/DeviceStateStore.ts +11 -52
- package/src/device/DeviceWalletSessionStore.ts +31 -4
- package/src/device/cloneDeviceState.ts +39 -0
- package/src/device/deviceIdentity.ts +2 -2
- package/src/deviceProfile/buildDeviceFeatures.ts +29 -88
- package/src/events/logBlockEvent.ts +8 -1
- package/src/inject.ts +14 -24
- package/src/protocols/protocol-v2/deviceSession.ts +24 -0
- package/src/protocols/protocol-v2/features.ts +37 -49
- package/src/protocols/protocol-v2/firmware.ts +1 -1
- package/src/protocols/protocol-v2/index.ts +2 -3
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +61 -0
- package/src/protocols/protocol-v2/uiInteraction.ts +4 -3
- package/src/protocols/protocol-v2/walletSession.ts +57 -146
- package/src/types/api/checkAllFirmwareRelease.ts +1 -2
- package/src/types/api/deviceSettings.ts +1 -0
- package/src/types/api/deviceUnlock.ts +1 -1
- package/src/types/api/firmwareUpdate.ts +5 -5
- package/src/types/api/getDeviceState.ts +1 -1
- package/src/types/api/getFeatures.ts +3 -0
- package/src/types/api/getOnekeyFeatures.ts +3 -0
- package/src/types/api/getPassphraseState.ts +6 -0
- package/src/types/api/index.ts +16 -23
- package/src/types/api/openWalletSession.ts +64 -0
- package/src/types/api/protocolV2.ts +36 -18
- package/src/types/device.ts +20 -14
- package/src/types/params.ts +1 -1
- package/src/types/settings.ts +8 -8
- package/src/utils/assets.ts +4 -1
- package/src/utils/deviceFeaturesCompat.ts +173 -0
- package/src/utils/deviceFeaturesUtils.ts +9 -8
- package/src/utils/deviceInfoUtils.ts +26 -64
- package/src/utils/deviceSettings.ts +18 -11
- package/src/utils/deviceVersionUtils.ts +25 -44
- package/src/utils/findDefectiveBatchDevice.ts +3 -3
- package/src/utils/index.ts +2 -5
- package/src/utils/networkUtils.ts +270 -14
- package/src/utils/pro2Wallpaper.ts +11 -13
- package/src/utils/tracing.ts +2 -2
- package/dist/api/internal.d.ts +0 -4
- package/dist/api/internal.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceSessionOpen.d.ts +0 -28
- package/dist/api/protocol-v2/DeviceSessionOpen.d.ts.map +0 -1
- package/src/api/internal.ts +0 -9
- package/src/api/protocol-v2/DeviceSessionOpen.ts +0 -100
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { ERRORS, HardwareError, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
import {
|
|
3
|
+
DeviceErrorCode,
|
|
4
|
+
type FailureType,
|
|
5
|
+
type Messages,
|
|
6
|
+
type Transport,
|
|
7
|
+
type TransportCallOptions,
|
|
8
|
+
} from '@onekeyfe/hd-transport';
|
|
2
9
|
|
|
3
10
|
import TransportManager from '../data-manager/TransportManager';
|
|
4
11
|
import DataManager from '../data-manager/DataManager';
|
|
@@ -12,12 +19,6 @@ import {
|
|
|
12
19
|
} from '../utils/tracing';
|
|
13
20
|
|
|
14
21
|
import type { Device } from './Device';
|
|
15
|
-
import type {
|
|
16
|
-
FailureType,
|
|
17
|
-
Messages,
|
|
18
|
-
Transport,
|
|
19
|
-
TransportCallOptions,
|
|
20
|
-
} from '@onekeyfe/hd-transport';
|
|
21
22
|
|
|
22
23
|
export type PassphrasePromptResponse = {
|
|
23
24
|
passphrase?: string;
|
|
@@ -51,11 +52,16 @@ const HIGH_VOLUME_DEBUG_CALLS = new Set([
|
|
|
51
52
|
'FirmwareUpload',
|
|
52
53
|
'ResourceAck',
|
|
53
54
|
]);
|
|
55
|
+
const SENSITIVE_DEBUG_CALLS = new Set(['DeviceSessionGet']);
|
|
54
56
|
|
|
55
57
|
function shouldReduceDebugForCall(type: string) {
|
|
56
58
|
return HIGH_VOLUME_DEBUG_CALLS.has(type);
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
function shouldBlockDebugForCall(type: string) {
|
|
62
|
+
return SENSITIVE_DEBUG_CALLS.has(type);
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
function getBinaryByteLength(value: unknown): number | undefined {
|
|
60
66
|
if (value instanceof ArrayBuffer) {
|
|
61
67
|
return value.byteLength;
|
|
@@ -98,6 +104,12 @@ function sanitizeDebugPayload(value: unknown, key = '', depth = 0): unknown {
|
|
|
98
104
|
return summarizeRedactedData(value);
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
// Redact passphrase-bearing keys as defense in depth so wallet identifiers and
|
|
108
|
+
// host passphrases cannot reach diagnostic logs through a future call path.
|
|
109
|
+
if (key && /passphrase/i.test(key)) {
|
|
110
|
+
return '[redacted passphrase]';
|
|
111
|
+
}
|
|
112
|
+
|
|
101
113
|
const byteLength = getBinaryByteLength(value);
|
|
102
114
|
if (byteLength !== undefined) {
|
|
103
115
|
return `[binary: ${byteLength} bytes]`;
|
|
@@ -141,10 +153,9 @@ function sanitizeDebugPayload(value: unknown, key = '', depth = 0): unknown {
|
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* 等与响应类型相关的选项。
|
|
156
|
+
* Interactive acknowledgements must not inherit timeoutMs from the original call.
|
|
157
|
+
* User confirmation and PIN/passphrase entry have unbounded think time. Preserve only
|
|
158
|
+
* response-related options such as expectedTypes and intermediateTypes.
|
|
148
159
|
*/
|
|
149
160
|
const stripInteractiveAckTimeout = (
|
|
150
161
|
options?: TransportCallOptions
|
|
@@ -440,6 +451,7 @@ export class DeviceCommands {
|
|
|
440
451
|
'PinMatrixAck',
|
|
441
452
|
'PassphraseAck',
|
|
442
453
|
'Cancel',
|
|
454
|
+
'DeviceSessionGet',
|
|
443
455
|
'BixinPinInputOnDevice',
|
|
444
456
|
'FilesystemFileRead',
|
|
445
457
|
'FilesystemFileWrite',
|
|
@@ -450,7 +462,7 @@ export class DeviceCommands {
|
|
|
450
462
|
'FirmwareUpload',
|
|
451
463
|
'ResourceAck',
|
|
452
464
|
] as any;
|
|
453
|
-
if (!skipTypes.includes(type) && msg) {
|
|
465
|
+
if (!skipTypes.includes(type) && !shouldBlockDebugForCall(type) && msg) {
|
|
454
466
|
// Use debug channel to avoid noise escalation
|
|
455
467
|
Log.debug('[DeviceCommands] [typedCall] Sending payload', type, sanitizeDebugPayload(msg));
|
|
456
468
|
}
|
|
@@ -475,12 +487,14 @@ export class DeviceCommands {
|
|
|
475
487
|
// throw bridge network error
|
|
476
488
|
if (error instanceof HardwareError) {
|
|
477
489
|
if (error.errorCode === HardwareErrorCode.ResponseUnexpectTypeError) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
490
|
+
if (!shouldBlockDebugForCall(type)) {
|
|
491
|
+
Log.debug('[DeviceCommands] [typedCall] Unexpected response type', {
|
|
492
|
+
request: type,
|
|
493
|
+
expected: resType,
|
|
494
|
+
received: response.type,
|
|
495
|
+
response: sanitizeDebugPayload(response.message),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
484
498
|
// Do not intercept CallMethodError
|
|
485
499
|
// Do not intercept “assertType: Response of unexpected type” error
|
|
486
500
|
// Blocking the above two messages will not know what the specific error message is, and the specific error should be handled by the subsequent business logic.
|
|
@@ -519,8 +533,8 @@ export class DeviceCommands {
|
|
|
519
533
|
options?: TransportCallOptions
|
|
520
534
|
): Promise<DefaultMessageResponse> {
|
|
521
535
|
try {
|
|
522
|
-
if (shouldReduceDebugForCall(callType)) {
|
|
523
|
-
//
|
|
536
|
+
if (shouldReduceDebugForCall(callType) || shouldBlockDebugForCall(callType)) {
|
|
537
|
+
// Skip high-volume or security-sensitive response payloads.
|
|
524
538
|
} else if (DataManager.getSettings('env') === 'react-native') {
|
|
525
539
|
Log.debug('_filterCommonTypes: ', JSON.stringify(sanitizeDebugPayload(res)));
|
|
526
540
|
} else {
|
|
@@ -578,9 +592,25 @@ export class DeviceCommands {
|
|
|
578
592
|
}
|
|
579
593
|
|
|
580
594
|
if (code === 'Failure_ProcessError') {
|
|
595
|
+
const normalizedMessage = message?.trim() ?? '';
|
|
596
|
+
const isLegacyProtocolV2ActionCancelledFailure =
|
|
597
|
+
this.device.isProtocolV2() &&
|
|
598
|
+
/^(?:cancelled on device|confirm dismissed)$/i.test(normalizedMessage);
|
|
581
599
|
const isLegacyProtocolV2LockedFailure =
|
|
582
|
-
this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(
|
|
583
|
-
if (
|
|
600
|
+
this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(normalizedMessage);
|
|
601
|
+
if (
|
|
602
|
+
subcode === DeviceErrorCode.DeviceError_ActionCancelled ||
|
|
603
|
+
isLegacyProtocolV2ActionCancelledFailure
|
|
604
|
+
) {
|
|
605
|
+
error = ERRORS.TypedError(HardwareErrorCode.ActionCancelled, message, {
|
|
606
|
+
failureCode: code,
|
|
607
|
+
subcode,
|
|
608
|
+
firmwareMessage: message,
|
|
609
|
+
});
|
|
610
|
+
} else if (
|
|
611
|
+
subcode === DeviceErrorCode.DeviceError_DeviceLocked ||
|
|
612
|
+
isLegacyProtocolV2LockedFailure
|
|
613
|
+
) {
|
|
584
614
|
error = ERRORS.TypedError(HardwareErrorCode.DeviceLocked, message, {
|
|
585
615
|
failureCode: code,
|
|
586
616
|
subcode,
|
|
@@ -10,11 +10,11 @@ export type DeviceFeaturesUpdateSource =
|
|
|
10
10
|
| 'cache';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Merge a field-level device-state patch.
|
|
14
14
|
*
|
|
15
|
-
* `undefined`
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* `undefined` means the response omitted a field and preserves the cache; `null`
|
|
16
|
+
* means the device explicitly returned an empty value and must be cached. Callers
|
|
17
|
+
* provide complete object fields such as raw; this function does not deep-merge.
|
|
18
18
|
*/
|
|
19
19
|
export function mergeDeviceFeaturesPatch(previous: Features, patch: Partial<Features>): Features {
|
|
20
20
|
let next = previous;
|
package/src/device/DevicePool.ts
CHANGED
|
@@ -160,7 +160,7 @@ export class DevicePool extends EventEmitter {
|
|
|
160
160
|
try {
|
|
161
161
|
await device.initialize(initOptions);
|
|
162
162
|
if (initOptions?.refreshRuntimeState && device.isProtocolV2()) {
|
|
163
|
-
await
|
|
163
|
+
await this._refreshProtocolV2DiscoveryState(device);
|
|
164
164
|
}
|
|
165
165
|
} finally {
|
|
166
166
|
await device.release();
|
|
@@ -175,9 +175,9 @@ export class DevicePool extends EventEmitter {
|
|
|
175
175
|
await device.run(
|
|
176
176
|
async () => {
|
|
177
177
|
try {
|
|
178
|
-
await
|
|
178
|
+
await this._refreshProtocolV2DiscoveryState(device);
|
|
179
179
|
} catch (error) {
|
|
180
|
-
// Device.run
|
|
180
|
+
// Device.run releases after the callback; then propagate the actual read error.
|
|
181
181
|
refreshError = error;
|
|
182
182
|
}
|
|
183
183
|
},
|
|
@@ -187,6 +187,20 @@ export class DevicePool extends EventEmitter {
|
|
|
187
187
|
if (refreshError) throw new Error(String(refreshError));
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Device lists require live mode and name data. A status failure remains a connection
|
|
192
|
+
* error; settings only supplies a label, so its failure retains the existing name.
|
|
193
|
+
* Device.getDeviceState skips unsupported status/settings calls in loader mode.
|
|
194
|
+
*/
|
|
195
|
+
static async _refreshProtocolV2DiscoveryState(device: Device) {
|
|
196
|
+
await device.getDeviceState({ refreshSections: ['status'] });
|
|
197
|
+
try {
|
|
198
|
+
await device.getDeviceState({ refreshSections: ['settings'] });
|
|
199
|
+
} catch (error) {
|
|
200
|
+
Log.debug('Unable to refresh Protocol V2 device label during discovery', error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
190
204
|
static async _checkDevicePool(initOptions?: InitOptions, connectId?: string) {
|
|
191
205
|
await this._sendConnectMessage(initOptions, connectId);
|
|
192
206
|
this._sendDisconnectMessage();
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import { buildProtocolV1FeaturesPayload } from '../deviceProfile/buildDeviceFeatures';
|
|
4
|
-
import {
|
|
5
|
-
isProtocolV2BootloaderDeviceInfo,
|
|
6
|
-
isProtocolV2RomloaderDeviceInfo,
|
|
7
|
-
} from '../protocols/protocol-v2/features';
|
|
4
|
+
import { mapLanguageToProtocolV2 } from '../utils/deviceSettings';
|
|
8
5
|
|
|
6
|
+
import type {
|
|
7
|
+
ApplySettings,
|
|
8
|
+
DeviceFirmwareImageInfo,
|
|
9
|
+
DeviceSettings,
|
|
10
|
+
DeviceStatus,
|
|
11
|
+
ProtocolV2DeviceInfo,
|
|
12
|
+
} from '@onekeyfe/hd-transport';
|
|
9
13
|
import type { PROTO } from '../constants';
|
|
10
14
|
import type {
|
|
11
15
|
DeviceFeaturesVerify,
|
|
@@ -15,13 +19,6 @@ import type {
|
|
|
15
19
|
OnekeyFeatures,
|
|
16
20
|
} from '../types';
|
|
17
21
|
import type { DeviceSettingsParams } from '../types/api/deviceSettings';
|
|
18
|
-
import type {
|
|
19
|
-
ApplySettings,
|
|
20
|
-
DeviceFirmwareImageInfo,
|
|
21
|
-
DeviceSettings,
|
|
22
|
-
DeviceStatus,
|
|
23
|
-
ProtocolV2DeviceInfo,
|
|
24
|
-
} from '@onekeyfe/hd-transport';
|
|
25
22
|
|
|
26
23
|
const definedEntries = <T extends Record<string, unknown>>(value: T): T =>
|
|
27
24
|
Object.fromEntries(
|
|
@@ -113,8 +110,9 @@ export const mapFeaturesToState = (features: Features): DeviceStatePatch => ({
|
|
|
113
110
|
bootloader: features.bootloaderVersion,
|
|
114
111
|
board: features.boardVersion,
|
|
115
112
|
ble: features.bleVersion,
|
|
113
|
+
// Legacy devices expose one SE version; project it to se01 and keep se as an alias.
|
|
116
114
|
se: features.seVersion,
|
|
117
|
-
se01: features.se01Version,
|
|
115
|
+
se01: features.se01Version ?? features.seVersion,
|
|
118
116
|
se02: features.se02Version,
|
|
119
117
|
se03: features.se03Version,
|
|
120
118
|
se04: features.se04Version,
|
|
@@ -125,10 +123,6 @@ export const mapFeaturesToState = (features: Features): DeviceStatePatch => ({
|
|
|
125
123
|
},
|
|
126
124
|
capabilities: features.capabilities,
|
|
127
125
|
verification: definedEntries(features.verify ?? {}),
|
|
128
|
-
session: {
|
|
129
|
-
sessionId: features.sessionId,
|
|
130
|
-
passphraseState: features.passphraseState,
|
|
131
|
-
},
|
|
132
126
|
raw: features.raw,
|
|
133
127
|
});
|
|
134
128
|
|
|
@@ -193,19 +187,9 @@ export const mapProtocolV1OnekeyFeaturesToState = (features: OnekeyFeatures): De
|
|
|
193
187
|
|
|
194
188
|
export const mapProtocolV2DeviceInfoToState = (
|
|
195
189
|
info: ProtocolV2DeviceInfo,
|
|
196
|
-
|
|
190
|
+
mode: DeviceStateMode = 'unknown'
|
|
197
191
|
): DeviceStatePatch => {
|
|
198
|
-
const
|
|
199
|
-
const bootloader = isProtocolV2BootloaderDeviceInfo(info);
|
|
200
|
-
const loader = romloader || bootloader;
|
|
201
|
-
let mode: DeviceStateMode = 'normal';
|
|
202
|
-
if (romloader) {
|
|
203
|
-
mode = 'romloader';
|
|
204
|
-
} else if (bootloader) {
|
|
205
|
-
mode = 'bootloader';
|
|
206
|
-
} else if (currentMode === 'notInitialized' || currentMode === 'backupMode') {
|
|
207
|
-
mode = currentMode;
|
|
208
|
-
}
|
|
192
|
+
const loader = mode === 'bootloader' || mode === 'romloader';
|
|
209
193
|
|
|
210
194
|
return {
|
|
211
195
|
protocol: 'V2',
|
|
@@ -350,7 +334,7 @@ export const mapCommonSettingsToProtocolV2 = (settings: DeviceSettingsParams): D
|
|
|
350
334
|
definedEntries({
|
|
351
335
|
label: settings.label,
|
|
352
336
|
bt_enable: settings.bluetoothEnabled,
|
|
353
|
-
language: settings.language,
|
|
337
|
+
language: mapLanguageToProtocolV2(settings.language),
|
|
354
338
|
brightness: settings.brightness,
|
|
355
339
|
autolock_delay_ms: settings.autoLockDelayMs,
|
|
356
340
|
autoshutdown_delay_ms: settings.autoShutdownDelayMs,
|
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { cloneDeviceState } from './cloneDeviceState';
|
|
2
|
+
|
|
3
|
+
import type { DeviceFeaturesRaw, DeviceState, Features } from '../types';
|
|
2
4
|
|
|
3
5
|
type StoredDeviceState = DeviceState & {
|
|
4
6
|
raw?: DeviceFeaturesRaw;
|
|
5
|
-
session?: DeviceStateSession;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
const getBootloaderMode = (state: StoredDeviceState) =>
|
|
9
10
|
state.status.mode === 'bootloader' || state.status.mode === 'romloader';
|
|
10
11
|
|
|
11
12
|
export const projectFeatures = (state: StoredDeviceState): Features => {
|
|
12
|
-
const snapshot =
|
|
13
|
+
const snapshot = cloneDeviceState(state);
|
|
13
14
|
const rawFeatures = snapshot.raw?.protocolV1Features ?? {};
|
|
15
|
+
const publicRawFeatures = { ...rawFeatures } as Record<string, unknown>;
|
|
16
|
+
delete publicRawFeatures.session_id;
|
|
17
|
+
delete publicRawFeatures.sessionId;
|
|
18
|
+
delete publicRawFeatures.passphraseState;
|
|
14
19
|
const rawOneKeyFeatures = snapshot.raw?.protocolV1OneKeyFeatures ?? {};
|
|
15
|
-
const sessionId = snapshot.session?.sessionId ?? null;
|
|
16
20
|
const bootloaderMode =
|
|
17
21
|
snapshot.protocol === 'V1'
|
|
18
22
|
? (rawFeatures as { bootloader_mode?: boolean | null }).bootloader_mode ?? null
|
|
19
23
|
: getBootloaderMode(snapshot);
|
|
20
24
|
|
|
21
25
|
return {
|
|
22
|
-
...
|
|
26
|
+
...publicRawFeatures,
|
|
23
27
|
...rawOneKeyFeatures,
|
|
24
28
|
protocol: snapshot.protocol,
|
|
25
29
|
protocolVersion: snapshot.protocolVersion ?? (snapshot.protocol === 'V1' ? 1 : null),
|
|
@@ -80,13 +84,12 @@ export const projectFeatures = (state: StoredDeviceState): Features => {
|
|
|
80
84
|
se04BootVersion: snapshot.versions.se04Boot,
|
|
81
85
|
seVersion: snapshot.versions.se ?? null,
|
|
82
86
|
verify: snapshot.verification,
|
|
83
|
-
sessionId,
|
|
84
|
-
passphraseState: snapshot.session?.passphraseState,
|
|
85
87
|
raw: snapshot.raw,
|
|
86
88
|
device_id: snapshot.identity.deviceId ?? undefined,
|
|
87
|
-
session_id: sessionId ?? undefined,
|
|
88
89
|
ble_name: snapshot.identity.bleName ?? undefined,
|
|
89
90
|
passphrase_protection: snapshot.status.passphraseProtection ?? undefined,
|
|
90
91
|
bootloader_mode: bootloaderMode,
|
|
92
|
+
sessionId: null,
|
|
93
|
+
session_id: null,
|
|
91
94
|
} as unknown as Features;
|
|
92
95
|
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
|
+
import { cloneDeviceState } from './cloneDeviceState';
|
|
4
|
+
|
|
3
5
|
import type {
|
|
4
6
|
DeviceFeaturesRaw,
|
|
5
7
|
DeviceState,
|
|
6
8
|
DeviceStateIdentity,
|
|
7
9
|
DeviceStatePatch,
|
|
8
|
-
DeviceStateSession,
|
|
9
10
|
DeviceStateUpdateSource,
|
|
10
11
|
} from '../types';
|
|
11
12
|
|
|
12
13
|
type StoredDeviceState = DeviceState & {
|
|
13
14
|
raw?: DeviceFeaturesRaw;
|
|
14
|
-
session?: DeviceStateSession;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export type DeviceStateUpdateResult = {
|
|
@@ -21,22 +21,6 @@ export type DeviceStateUpdateResult = {
|
|
|
21
21
|
changedKeys: string[];
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
const PRODUCT_DISPLAY_NAMES: Partial<Record<DeviceStateIdentity['deviceType'], string>> = {
|
|
25
|
-
[EDeviceType.Classic]: 'OneKey Classic',
|
|
26
|
-
[EDeviceType.Classic1s]: 'OneKey Classic 1S',
|
|
27
|
-
[EDeviceType.ClassicPure]: 'OneKey Classic 1S Pure',
|
|
28
|
-
[EDeviceType.Mini]: 'OneKey Mini',
|
|
29
|
-
[EDeviceType.Touch]: 'OneKey Touch',
|
|
30
|
-
[EDeviceType.Pro]: 'OneKey Pro',
|
|
31
|
-
[EDeviceType.Pro2]: 'OneKey Pro 2',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const getModelDisplayName = (deviceType: DeviceStateIdentity['deviceType']) =>
|
|
35
|
-
PRODUCT_DISPLAY_NAMES[deviceType] ?? 'OneKey';
|
|
36
|
-
|
|
37
|
-
const getDisplayName = (identity: DeviceStateIdentity) =>
|
|
38
|
-
identity.label || identity.bleName || getModelDisplayName(identity.deviceType);
|
|
39
|
-
|
|
40
24
|
export function createEmptyDeviceState(
|
|
41
25
|
identity: Partial<DeviceStateIdentity> = {}
|
|
42
26
|
): StoredDeviceState {
|
|
@@ -49,10 +33,8 @@ export function createEmptyDeviceState(
|
|
|
49
33
|
serialNo: '',
|
|
50
34
|
label: null,
|
|
51
35
|
bleName: null,
|
|
52
|
-
displayName: '',
|
|
53
36
|
...identity,
|
|
54
37
|
};
|
|
55
|
-
normalizedIdentity.displayName = getDisplayName(normalizedIdentity);
|
|
56
38
|
|
|
57
39
|
return {
|
|
58
40
|
schemaVersion: 1,
|
|
@@ -128,7 +110,7 @@ export class DeviceStateStore {
|
|
|
128
110
|
private state: StoredDeviceState | undefined;
|
|
129
111
|
|
|
130
112
|
constructor(initial?: StoredDeviceState) {
|
|
131
|
-
this.state = initial ?
|
|
113
|
+
this.state = initial ? cloneDeviceState(initial) : undefined;
|
|
132
114
|
}
|
|
133
115
|
|
|
134
116
|
getState() {
|
|
@@ -138,7 +120,7 @@ export class DeviceStateStore {
|
|
|
138
120
|
replace(state: StoredDeviceState, source: DeviceStateUpdateSource): DeviceStateUpdateResult {
|
|
139
121
|
const revision = (this.state?.revision ?? 0) + 1;
|
|
140
122
|
this.state = {
|
|
141
|
-
...
|
|
123
|
+
...cloneDeviceState(state),
|
|
142
124
|
revision,
|
|
143
125
|
updatedAt: Date.now(),
|
|
144
126
|
};
|
|
@@ -151,7 +133,7 @@ export class DeviceStateStore {
|
|
|
151
133
|
}
|
|
152
134
|
|
|
153
135
|
update(patch: DeviceStatePatch, source: DeviceStateUpdateSource): DeviceStateUpdateResult {
|
|
154
|
-
const next =
|
|
136
|
+
const next = cloneDeviceState(this.state ?? createEmptyDeviceState());
|
|
155
137
|
const changedKeys: string[] = [];
|
|
156
138
|
|
|
157
139
|
if (patch.protocol !== undefined && patch.protocol !== next.protocol) {
|
|
@@ -168,13 +150,13 @@ export class DeviceStateStore {
|
|
|
168
150
|
if (patch.versions) applySectionPatch(next.versions, patch.versions, 'versions', changedKeys);
|
|
169
151
|
|
|
170
152
|
if (patch.capabilities !== undefined && !isEqual(next.capabilities, patch.capabilities)) {
|
|
171
|
-
next.capabilities =
|
|
153
|
+
next.capabilities = cloneDeviceState(patch.capabilities);
|
|
172
154
|
changedKeys.push('capabilities');
|
|
173
155
|
}
|
|
174
156
|
if (patch.verification !== undefined) {
|
|
175
157
|
const mergedVerification = {
|
|
176
|
-
...
|
|
177
|
-
...
|
|
158
|
+
...cloneDeviceState(next.verification ?? {}),
|
|
159
|
+
...cloneDeviceState(patch.verification),
|
|
178
160
|
};
|
|
179
161
|
if (!isEqual(next.verification, mergedVerification)) {
|
|
180
162
|
next.verification = mergedVerification;
|
|
@@ -182,12 +164,12 @@ export class DeviceStateStore {
|
|
|
182
164
|
}
|
|
183
165
|
}
|
|
184
166
|
if (patch.raw !== undefined) {
|
|
185
|
-
const mergedRaw =
|
|
167
|
+
const mergedRaw = cloneDeviceState(next.raw ?? {});
|
|
186
168
|
for (const [key, value] of Object.entries(patch.raw)) {
|
|
187
169
|
if (value === null) {
|
|
188
170
|
delete mergedRaw[key as keyof typeof mergedRaw];
|
|
189
171
|
} else if (value !== undefined) {
|
|
190
|
-
mergedRaw[key as keyof typeof mergedRaw] =
|
|
172
|
+
mergedRaw[key as keyof typeof mergedRaw] = cloneDeviceState(value) as never;
|
|
191
173
|
}
|
|
192
174
|
}
|
|
193
175
|
if (!isEqual(next.raw, mergedRaw)) {
|
|
@@ -195,23 +177,6 @@ export class DeviceStateStore {
|
|
|
195
177
|
changedKeys.push('raw');
|
|
196
178
|
}
|
|
197
179
|
}
|
|
198
|
-
if (patch.session === null) {
|
|
199
|
-
if (next.session !== undefined) {
|
|
200
|
-
delete next.session;
|
|
201
|
-
changedKeys.push('session');
|
|
202
|
-
}
|
|
203
|
-
} else if (patch.session) {
|
|
204
|
-
const session = next.session ?? { sessionId: null };
|
|
205
|
-
applySectionPatch(session, patch.session, 'session', changedKeys);
|
|
206
|
-
next.session = session;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const displayName = getDisplayName(next.identity);
|
|
210
|
-
if (displayName !== next.identity.displayName) {
|
|
211
|
-
next.identity.displayName = displayName;
|
|
212
|
-
changedKeys.push('identity.displayName');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
180
|
if (changedKeys.length > 0) {
|
|
216
181
|
next.revision = (this.state?.revision ?? 0) + 1;
|
|
217
182
|
next.updatedAt = Date.now();
|
|
@@ -227,16 +192,10 @@ export class DeviceStateStore {
|
|
|
227
192
|
changedKeys,
|
|
228
193
|
};
|
|
229
194
|
}
|
|
230
|
-
|
|
231
|
-
clearSession(source: DeviceStateUpdateSource) {
|
|
232
|
-
if (!this.state?.session) return undefined;
|
|
233
|
-
return this.update({ session: null }, source);
|
|
234
|
-
}
|
|
235
195
|
}
|
|
236
196
|
|
|
237
197
|
export const createPublicDeviceState = (state: StoredDeviceState): DeviceState => {
|
|
238
|
-
const snapshot =
|
|
198
|
+
const snapshot = cloneDeviceState(state);
|
|
239
199
|
delete snapshot.raw;
|
|
240
|
-
delete snapshot.session;
|
|
241
200
|
return snapshot;
|
|
242
201
|
};
|
|
@@ -49,7 +49,7 @@ export class DeviceWalletSessionStore {
|
|
|
49
49
|
this.pendingSessions.delete(deviceKey);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
private migrateTemporaryDeviceKey(from: string, to: string) {
|
|
53
53
|
if (!from || !to || from === to) return;
|
|
54
54
|
|
|
55
55
|
const sourceSessions = this.walletSessions.get(from);
|
|
@@ -57,15 +57,42 @@ export class DeviceWalletSessionStore {
|
|
|
57
57
|
const targetSessions = this.walletSessions.get(to) ?? new Map<string, string>();
|
|
58
58
|
this.walletSessions.set(to, targetSessions);
|
|
59
59
|
sourceSessions.forEach((sessionId, passphraseState) => {
|
|
60
|
-
targetSessions.
|
|
60
|
+
if (!targetSessions.has(passphraseState)) {
|
|
61
|
+
targetSessions.set(passphraseState, sessionId);
|
|
62
|
+
}
|
|
61
63
|
});
|
|
62
64
|
this.walletSessions.delete(from);
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
const pendingSession = this.pendingSessions.get(from);
|
|
66
|
-
if (pendingSession) {
|
|
68
|
+
if (pendingSession && !this.pendingSessions.has(to)) {
|
|
67
69
|
this.pendingSessions.set(to, pendingSession);
|
|
68
|
-
|
|
70
|
+
}
|
|
71
|
+
this.pendingSessions.delete(from);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
reconcileDeviceIdentity({
|
|
75
|
+
temporaryKey,
|
|
76
|
+
previousDeviceId,
|
|
77
|
+
nextDeviceId,
|
|
78
|
+
}: {
|
|
79
|
+
temporaryKey?: string;
|
|
80
|
+
previousDeviceId?: string;
|
|
81
|
+
nextDeviceId?: string;
|
|
82
|
+
}) {
|
|
83
|
+
if (!nextDeviceId) return;
|
|
84
|
+
|
|
85
|
+
// A stable identity changing from A to B means the descriptor now refers to a
|
|
86
|
+
// different physical device. Never copy A's wallet sessions into B's cache.
|
|
87
|
+
if (previousDeviceId && previousDeviceId !== nextDeviceId) {
|
|
88
|
+
this.deleteDevice(previousDeviceId);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Migration is allowed only for the first promotion from a transport-local
|
|
93
|
+
// descriptor key to a firmware-provided stable identity.
|
|
94
|
+
if (!previousDeviceId && temporaryKey) {
|
|
95
|
+
this.migrateTemporaryDeviceKey(temporaryKey, nextDeviceId);
|
|
69
96
|
}
|
|
70
97
|
}
|
|
71
98
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-independent deep clone for DeviceState snapshots.
|
|
3
|
+
*
|
|
4
|
+
* Avoid the global structuredClone API because older Hermes and WebView hosts do not
|
|
5
|
+
* provide it. Explicitly clone plain objects, arrays, binary buffers, and common
|
|
6
|
+
* built-ins so initialization and state snapshots remain portable.
|
|
7
|
+
*/
|
|
8
|
+
export const cloneDeviceState = <T>(value: T): T => {
|
|
9
|
+
if (value === null || typeof value !== 'object') {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Copy underlying binary memory so external mutation cannot alter a snapshot.
|
|
14
|
+
if (value instanceof Uint8Array) {
|
|
15
|
+
return new Uint8Array(value) as unknown as T;
|
|
16
|
+
}
|
|
17
|
+
if (value instanceof ArrayBuffer) {
|
|
18
|
+
return value.slice(0) as unknown as T;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (value instanceof Date) {
|
|
22
|
+
return new Date(value.getTime()) as unknown as T;
|
|
23
|
+
}
|
|
24
|
+
if (value instanceof RegExp) {
|
|
25
|
+
return new RegExp(value.source, value.flags) as unknown as T;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value.map(item => cloneDeviceState(item)) as unknown as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// DeviceState contains only plain objects at this boundary.
|
|
33
|
+
const cloned: Record<string, unknown> = {};
|
|
34
|
+
const source = value as Record<string, unknown>;
|
|
35
|
+
for (const key of Object.keys(source)) {
|
|
36
|
+
cloned[key] = cloneDeviceState(source[key]);
|
|
37
|
+
}
|
|
38
|
+
return cloned as unknown as T;
|
|
39
|
+
};
|
|
@@ -3,8 +3,8 @@ import type { Device } from './Device';
|
|
|
3
3
|
type DeviceIdentityReader = Pick<Device, 'isProtocolV2' | 'getDeviceState' | 'checkDeviceId'>;
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Protocol V2
|
|
7
|
-
* Protocol V1
|
|
6
|
+
* Protocol V2 deviceId comes from runtime state and requires a live status read.
|
|
7
|
+
* Protocol V1 reuses the identity returned by Initialize/GetFeatures.
|
|
8
8
|
*/
|
|
9
9
|
export async function checkLiveDeviceId(
|
|
10
10
|
device: DeviceIdentityReader,
|