@onekeyfe/hd-core 1.2.0-alpha.34 → 1.2.0-alpha.36
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 +74 -0
- package/__tests__/DeviceCommands.test.ts +84 -0
- package/__tests__/DeviceEventRegistration.test.ts +6 -0
- package/__tests__/device-settings.test.ts +3 -0
- package/__tests__/device-wallet-session-store.test.ts +80 -7
- package/__tests__/evmSignTransaction.test.ts +69 -0
- package/__tests__/get-device-state.test.ts +180 -40
- package/__tests__/open-wallet-session.test.ts +314 -55
- package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
- package/__tests__/protocol-v2.test.ts +768 -139
- package/dist/api/FirmwareUpdateV4.d.ts +1 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.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/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/evm/protocol.d.ts +3 -0
- package/dist/api/evm/protocol.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
- package/dist/core/deviceEventRegistration.d.ts +2 -0
- package/dist/core/deviceEventRegistration.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +25 -6
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +0 -1
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
- package/dist/events/device.d.ts +4 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +27 -2
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +63 -12
- package/dist/index.js +1164 -904
- package/dist/protocols/protocol-v2/features.d.ts +17 -6
- 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 +1 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -1
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +2 -2
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +2 -1
- package/dist/types/device.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +1 -1
- package/src/api/FirmwareUpdateV4.ts +39 -14
- package/src/api/GetPassphraseState.ts +17 -0
- package/src/api/OpenWalletSession.ts +31 -8
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +24 -2
- package/src/api/device/DeviceUnlock.ts +1 -1
- package/src/api/evm/EVMGetAddress.ts +2 -2
- package/src/api/evm/EVMGetPublicKey.ts +2 -2
- package/src/api/evm/EVMSignMessage.ts +2 -2
- package/src/api/evm/EVMSignTransaction.ts +2 -2
- package/src/api/evm/EVMSignTypedData.ts +35 -45
- package/src/api/evm/EVMVerifyMessage.ts +2 -2
- package/src/api/evm/protocol.ts +6 -0
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
- package/src/core/deviceEventRegistration.ts +4 -0
- package/src/core/index.ts +46 -4
- package/src/data/messages/messages-protocol-v2.json +408 -485
- package/src/device/Device.ts +266 -43
- package/src/device/DeviceCommands.ts +12 -2
- package/src/device/DeviceWalletSessionStore.ts +0 -14
- package/src/deviceProfile/buildDeviceFeatures.ts +1 -1
- package/src/events/device.ts +4 -0
- package/src/events/ui-request.ts +32 -2
- package/src/protocols/protocol-v2/features.ts +65 -16
- package/src/protocols/protocol-v2/index.ts +5 -0
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
- package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
- package/src/protocols/protocol-v2/unlockRetry.ts +51 -18
- package/src/protocols/protocol-v2/walletSession.ts +172 -64
- package/src/types/api/openWalletSession.ts +1 -1
- package/src/types/api/protocolV2.ts +2 -2
- package/src/types/device.ts +2 -0
- package/src/utils/deviceFeaturesUtils.ts +4 -0
package/src/device/Device.ts
CHANGED
|
@@ -55,6 +55,7 @@ import { DataManager } from '../data-manager';
|
|
|
55
55
|
import TransportManager from '../data-manager/TransportManager';
|
|
56
56
|
import { toHardened } from '../api/helpers/pathUtils';
|
|
57
57
|
import {
|
|
58
|
+
PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE,
|
|
58
59
|
PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST,
|
|
59
60
|
PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST,
|
|
60
61
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
@@ -62,21 +63,27 @@ import {
|
|
|
62
63
|
getProtocolV2RuntimeMode,
|
|
63
64
|
requestProtocolV2DeviceInfo,
|
|
64
65
|
requestProtocolV2DeviceStatus,
|
|
66
|
+
requestProtocolV2ProtocolInfo,
|
|
67
|
+
supportsProtocolV2Message,
|
|
65
68
|
} from '../protocols/protocol-v2/features';
|
|
66
69
|
import { buildProtocolV1FeaturesPayload } from '../deviceProfile';
|
|
70
|
+
import { resolveProtocolV2DeviceIdentity } from '../deviceProfile/protocolV2DeviceIdentity';
|
|
67
71
|
|
|
68
72
|
import type { PROTO } from '../constants';
|
|
69
73
|
import type { DeviceStateReadOptions } from '../types/api/getDeviceState';
|
|
70
74
|
import type {
|
|
71
75
|
DeviceButtonRequestPayload,
|
|
72
76
|
DeviceFeaturesPayload,
|
|
77
|
+
HardwareUiInteractionMeta,
|
|
73
78
|
PassphraseRequestPayload,
|
|
79
|
+
ProtocolV2UiEventMetadata,
|
|
74
80
|
} from '../events';
|
|
75
81
|
import type { PassphrasePromptResponse } from './DeviceCommands';
|
|
76
82
|
import type { Deferred, HardwareConnectProtocol } from '@onekeyfe/hd-shared';
|
|
77
83
|
import type {
|
|
78
84
|
OneKeyDeviceInfo as DeviceDescriptor,
|
|
79
85
|
DeviceStatus,
|
|
86
|
+
ProtocolInfo,
|
|
80
87
|
ProtocolV2DeviceInfo,
|
|
81
88
|
Success,
|
|
82
89
|
} from '@onekeyfe/hd-transport';
|
|
@@ -105,8 +112,30 @@ const parseRunOptions = (options?: RunOptions): RunOptions => {
|
|
|
105
112
|
|
|
106
113
|
const Log = getLogger(LoggerNames.Device);
|
|
107
114
|
|
|
115
|
+
const isProtocolV2DeviceStatusUnsupportedError = (error: unknown) => {
|
|
116
|
+
if (error instanceof HardwareError) {
|
|
117
|
+
if (error.errorCode === HardwareErrorCode.DeviceNotSupportMethod) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (error.params?.failureCode === 'Failure_UnexpectedMessage') {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const message =
|
|
126
|
+
error instanceof Error
|
|
127
|
+
? error.message
|
|
128
|
+
: String((error as { message?: unknown } | null)?.message ?? error ?? '');
|
|
129
|
+
return (
|
|
130
|
+
/^Failure_UnexpectedMessage(?:,|\b)/i.test(message) ||
|
|
131
|
+
/\b(?:unsupported message|handler not registered|message handler not found)\b/i.test(message)
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
108
135
|
export interface DeviceEvents {
|
|
109
136
|
[DEVICE.PIN]: [Device, PROTO.PinMatrixRequestType | undefined, (err: any, pin: string) => void];
|
|
137
|
+
[DEVICE.PIN_ON_DEVICE]: [Device, DeviceSessionPinType, ProtocolV2UiEventMetadata?];
|
|
138
|
+
[DEVICE.PIN_ON_DEVICE_COMPLETE]: [Device, HardwareUiInteractionMeta];
|
|
110
139
|
[DEVICE.PASSPHRASE_ON_DEVICE]: [Device, PassphraseRequestPayload?];
|
|
111
140
|
[DEVICE.ATTACH_PIN_ON_DEVICE]: [Device, PassphraseRequestPayload?];
|
|
112
141
|
[DEVICE.BUTTON]: [Device, DeviceButtonRequestPayload];
|
|
@@ -205,6 +234,24 @@ export class Device extends EventEmitter {
|
|
|
205
234
|
/** Force the next initialization to reload DeviceInfo after reconnect or reboot. */
|
|
206
235
|
private protocolV2StateNeedsReload = false;
|
|
207
236
|
|
|
237
|
+
/** Runtime context negotiated once per active Protocol V2 link. */
|
|
238
|
+
private protocolV2RuntimeContext?: ProtocolInfo;
|
|
239
|
+
|
|
240
|
+
/** Coalesces concurrent first-use runtime negotiation on the same active link. */
|
|
241
|
+
private protocolV2RuntimeContextPromise?: Promise<ProtocolInfo>;
|
|
242
|
+
|
|
243
|
+
/** Invalidates an in-flight runtime-context response without adding a transport epoch. */
|
|
244
|
+
private protocolV2RuntimeContextRequestToken?: object;
|
|
245
|
+
|
|
246
|
+
private protocolV2UiInteraction?: {
|
|
247
|
+
interactionId: string;
|
|
248
|
+
phaseCounter: number;
|
|
249
|
+
sequence: number;
|
|
250
|
+
opened: boolean;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
private protocolV2UiInteractionCounter = 0;
|
|
254
|
+
|
|
208
255
|
get state() {
|
|
209
256
|
return this.stateStore.getState();
|
|
210
257
|
}
|
|
@@ -835,8 +882,8 @@ export class Device extends EventEmitter {
|
|
|
835
882
|
|
|
836
883
|
const expectedDeviceId = options?.deviceId;
|
|
837
884
|
if (expectedDeviceId) {
|
|
838
|
-
//
|
|
839
|
-
//
|
|
885
|
+
// 先只读校验物理设备身份;钱包上下文仍由下方携带完整参数的
|
|
886
|
+
// Initialize 选择,避免标准钱包请求复用此前的隐藏钱包上下文。
|
|
840
887
|
this.passphraseState = undefined;
|
|
841
888
|
const { message } = await this.commands.typedCall('GetFeatures', 'Features', {});
|
|
842
889
|
this._updateFeatures(message);
|
|
@@ -864,15 +911,7 @@ export class Device extends EventEmitter {
|
|
|
864
911
|
payload.derive_cardano = true;
|
|
865
912
|
}
|
|
866
913
|
|
|
867
|
-
|
|
868
|
-
!expectedDeviceId ||
|
|
869
|
-
Boolean(internalState) ||
|
|
870
|
-
Boolean(options?.passphraseState) ||
|
|
871
|
-
options?.deriveCardano === true ||
|
|
872
|
-
options?.initSession === true;
|
|
873
|
-
if (requiresWalletInitialize) {
|
|
874
|
-
await callInitialize(payload, options?.initSession);
|
|
875
|
-
}
|
|
914
|
+
await callInitialize(payload, options?.initSession);
|
|
876
915
|
} catch (error) {
|
|
877
916
|
Log.error('Initialization failed:', error);
|
|
878
917
|
throw error;
|
|
@@ -970,13 +1009,7 @@ export class Device extends EventEmitter {
|
|
|
970
1009
|
}
|
|
971
1010
|
|
|
972
1011
|
if (refresh.has('status') && !initializedWithDeviceInfo) {
|
|
973
|
-
|
|
974
|
-
refreshedDeviceInfo ??
|
|
975
|
-
(await requestProtocolV2DeviceInfo({
|
|
976
|
-
commands: this.commands,
|
|
977
|
-
request: getProtocolV2DeviceInfoRequest(),
|
|
978
|
-
}));
|
|
979
|
-
await this.probeProtocolV2RuntimeState(deviceInfo);
|
|
1012
|
+
await this.probeProtocolV2RuntimeState(refreshedDeviceInfo);
|
|
980
1013
|
}
|
|
981
1014
|
|
|
982
1015
|
if (refresh.has('settings') && this.state?.status.mode === 'normal') {
|
|
@@ -1047,7 +1080,83 @@ export class Device extends EventEmitter {
|
|
|
1047
1080
|
return this.features;
|
|
1048
1081
|
}
|
|
1049
1082
|
|
|
1050
|
-
async
|
|
1083
|
+
async ensureProtocolV2RuntimeContext(timeoutMs?: number): Promise<ProtocolInfo> {
|
|
1084
|
+
const cachedProtocolInfo =
|
|
1085
|
+
this.protocolV2RuntimeContext ??
|
|
1086
|
+
(!this.protocolV2StateNeedsReload
|
|
1087
|
+
? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
|
|
1088
|
+
: undefined);
|
|
1089
|
+
if (cachedProtocolInfo) {
|
|
1090
|
+
this.protocolV2RuntimeContext = cachedProtocolInfo;
|
|
1091
|
+
return cachedProtocolInfo;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (this.protocolV2RuntimeContextPromise) {
|
|
1095
|
+
return this.protocolV2RuntimeContextPromise;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const requestToken = {};
|
|
1099
|
+
const pendingRequest = (async () => {
|
|
1100
|
+
const protocolInfo = await requestProtocolV2ProtocolInfo({
|
|
1101
|
+
commands: this.commands,
|
|
1102
|
+
timeoutMs,
|
|
1103
|
+
});
|
|
1104
|
+
if (this.protocolV2RuntimeContextRequestToken !== requestToken) {
|
|
1105
|
+
throw ERRORS.TypedError(
|
|
1106
|
+
HardwareErrorCode.DeviceInitializeFailed,
|
|
1107
|
+
'Protocol V2 runtime context was invalidated while loading.'
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
this.protocolV2RuntimeContext = protocolInfo;
|
|
1111
|
+
return protocolInfo;
|
|
1112
|
+
})();
|
|
1113
|
+
this.protocolV2RuntimeContextRequestToken = requestToken;
|
|
1114
|
+
this.protocolV2RuntimeContextPromise = pendingRequest;
|
|
1115
|
+
|
|
1116
|
+
try {
|
|
1117
|
+
return await pendingRequest;
|
|
1118
|
+
} finally {
|
|
1119
|
+
if (this.protocolV2RuntimeContextPromise === pendingRequest) {
|
|
1120
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1121
|
+
}
|
|
1122
|
+
if (this.protocolV2RuntimeContextRequestToken === requestToken) {
|
|
1123
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
async probeProtocolV2RuntimeState(deviceInfo?: ProtocolV2DeviceInfo, timeoutMs?: number) {
|
|
1129
|
+
const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs);
|
|
1130
|
+
const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
|
|
1131
|
+
const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
|
|
1132
|
+
const protocolV2DeviceType = runtimeDeviceInfo
|
|
1133
|
+
? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
|
|
1134
|
+
: this.getCurrentDeviceType();
|
|
1135
|
+
if (runtimeMode === 'romloader' && protocolV2DeviceType !== EDeviceType.Pro2) {
|
|
1136
|
+
throw ERRORS.TypedError(
|
|
1137
|
+
HardwareErrorCode.DeviceInitializeFailed,
|
|
1138
|
+
'Protocol V2 romloader mode is only supported for Pro2.'
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
const deviceStatusSupported = supportsProtocolV2Message(
|
|
1142
|
+
protocolInfo,
|
|
1143
|
+
PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE
|
|
1144
|
+
);
|
|
1145
|
+
|
|
1146
|
+
if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
|
|
1147
|
+
return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (!deviceStatusSupported) {
|
|
1151
|
+
if (runtimeMode === 'normal') {
|
|
1152
|
+
return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
|
|
1153
|
+
}
|
|
1154
|
+
throw ERRORS.TypedError(
|
|
1155
|
+
HardwareErrorCode.DeviceInitializeFailed,
|
|
1156
|
+
`Unknown Protocol V2 build fingerprint without DeviceStatusGet capability: ${protocolInfo.build_fingerprint}`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1051
1160
|
let deviceStatus: DeviceStatus;
|
|
1052
1161
|
try {
|
|
1053
1162
|
deviceStatus = await requestProtocolV2DeviceStatus({
|
|
@@ -1055,33 +1164,28 @@ export class Device extends EventEmitter {
|
|
|
1055
1164
|
timeoutMs,
|
|
1056
1165
|
});
|
|
1057
1166
|
} catch (error) {
|
|
1058
|
-
|
|
1059
|
-
deviceInfo,
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
Log.debug('Protocol V2 DeviceStatusGet unavailable; use temporary loader fallback', {
|
|
1063
|
-
runtimeMode,
|
|
1064
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1065
|
-
});
|
|
1066
|
-
return this.updateProtocolV2Features(deviceInfo, null, runtimeMode);
|
|
1167
|
+
if (runtimeMode === undefined && isProtocolV2DeviceStatusUnsupportedError(error)) {
|
|
1168
|
+
return this.updateProtocolV2Features(deviceInfo, null, 'bootloader', protocolInfo);
|
|
1169
|
+
}
|
|
1170
|
+
throw error;
|
|
1067
1171
|
}
|
|
1068
|
-
return this.updateProtocolV2Features(deviceInfo, deviceStatus, 'normal');
|
|
1172
|
+
return this.updateProtocolV2Features(deviceInfo, deviceStatus, 'normal', protocolInfo);
|
|
1069
1173
|
}
|
|
1070
1174
|
|
|
1071
1175
|
updateProtocolV2Features(
|
|
1072
1176
|
deviceInfo?: ProtocolV2DeviceInfo,
|
|
1073
1177
|
deviceStatus?: DeviceStatus | null,
|
|
1074
|
-
runtimeMode?: ProtocolV2RuntimeMode
|
|
1178
|
+
runtimeMode?: ProtocolV2RuntimeMode,
|
|
1179
|
+
protocolInfo?: ProtocolInfo
|
|
1075
1180
|
) {
|
|
1076
1181
|
const previousDeviceId = this.getCurrentDeviceId();
|
|
1077
|
-
const resolvedMode =
|
|
1078
|
-
runtimeMode ??
|
|
1079
|
-
(deviceStatus
|
|
1080
|
-
? getProtocolV2RuntimeMode({ deviceInfo, deviceStatusAvailable: true })
|
|
1081
|
-
: this.state?.status.mode);
|
|
1182
|
+
const resolvedMode = runtimeMode ?? (deviceStatus ? 'normal' : this.state?.status.mode);
|
|
1082
1183
|
if (deviceInfo) {
|
|
1083
1184
|
this.updateState(mapProtocolV2DeviceInfoToState(deviceInfo, resolvedMode), 'device-info');
|
|
1084
1185
|
}
|
|
1186
|
+
if (protocolInfo) {
|
|
1187
|
+
this.updateState({ raw: { protocolV2ProtocolInfo: protocolInfo } }, 'device-info');
|
|
1188
|
+
}
|
|
1085
1189
|
if (deviceStatus) {
|
|
1086
1190
|
this.updateState(mapProtocolV2DeviceStatusToState(deviceStatus), 'device-status');
|
|
1087
1191
|
}
|
|
@@ -1102,6 +1206,9 @@ export class Device extends EventEmitter {
|
|
|
1102
1206
|
this.deviceAcquired = false;
|
|
1103
1207
|
if (!this.isProtocolV2()) return;
|
|
1104
1208
|
this.protocolV2StateNeedsReload = true;
|
|
1209
|
+
this.protocolV2RuntimeContext = undefined;
|
|
1210
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1211
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1105
1212
|
this.clearPreInitialized();
|
|
1106
1213
|
}
|
|
1107
1214
|
|
|
@@ -1110,9 +1217,14 @@ export class Device extends EventEmitter {
|
|
|
1110
1217
|
if (deviceId) {
|
|
1111
1218
|
deviceWalletSessionStore.deleteDevice(deviceId);
|
|
1112
1219
|
}
|
|
1113
|
-
if (this.isProtocolV2()
|
|
1114
|
-
|
|
1220
|
+
if (this.isProtocolV2()) {
|
|
1221
|
+
if (this.originalDescriptor.path !== deviceId) {
|
|
1222
|
+
deviceWalletSessionStore.deleteDevice(this.originalDescriptor.path);
|
|
1223
|
+
}
|
|
1115
1224
|
this.protocolV2StateNeedsReload = true;
|
|
1225
|
+
this.protocolV2RuntimeContext = undefined;
|
|
1226
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1227
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1116
1228
|
}
|
|
1117
1229
|
|
|
1118
1230
|
this.passphraseState = undefined;
|
|
@@ -1125,6 +1237,9 @@ export class Device extends EventEmitter {
|
|
|
1125
1237
|
if (!this.isProtocolV2()) return;
|
|
1126
1238
|
|
|
1127
1239
|
this.protocolV2StateNeedsReload = true;
|
|
1240
|
+
this.protocolV2RuntimeContext = undefined;
|
|
1241
|
+
this.protocolV2RuntimeContextPromise = undefined;
|
|
1242
|
+
this.protocolV2RuntimeContextRequestToken = undefined;
|
|
1128
1243
|
this.clearPreInitialized();
|
|
1129
1244
|
let loaderMode: 'bootloader' | 'romloader' | undefined;
|
|
1130
1245
|
if (rebootType === DeviceRebootType.Bootloader) {
|
|
@@ -1150,7 +1265,7 @@ export class Device extends EventEmitter {
|
|
|
1150
1265
|
attachToPinEnabled: null,
|
|
1151
1266
|
unlockedAttachPin: null,
|
|
1152
1267
|
},
|
|
1153
|
-
raw: { protocolV2DeviceStatus: null },
|
|
1268
|
+
raw: { protocolV2ProtocolInfo: null, protocolV2DeviceStatus: null },
|
|
1154
1269
|
},
|
|
1155
1270
|
'transport-reconnect'
|
|
1156
1271
|
);
|
|
@@ -1160,7 +1275,7 @@ export class Device extends EventEmitter {
|
|
|
1160
1275
|
this.updateState(
|
|
1161
1276
|
{
|
|
1162
1277
|
status: { mode: 'normal', unlocked: null },
|
|
1163
|
-
raw: { protocolV2DeviceStatus: null },
|
|
1278
|
+
raw: { protocolV2ProtocolInfo: null, protocolV2DeviceStatus: null },
|
|
1164
1279
|
},
|
|
1165
1280
|
'transport-reconnect'
|
|
1166
1281
|
);
|
|
@@ -1406,7 +1521,16 @@ export class Device extends EventEmitter {
|
|
|
1406
1521
|
|
|
1407
1522
|
isBootloader() {
|
|
1408
1523
|
if (!this.state) return undefined;
|
|
1409
|
-
return this.state.status.mode === 'bootloader'
|
|
1524
|
+
return this.state.status.mode === 'bootloader';
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
isRomloader() {
|
|
1528
|
+
if (!this.state) return undefined;
|
|
1529
|
+
return (
|
|
1530
|
+
this.isProtocolV2() &&
|
|
1531
|
+
this.getCurrentDeviceType() === EDeviceType.Pro2 &&
|
|
1532
|
+
this.state.status.mode === 'romloader'
|
|
1533
|
+
);
|
|
1410
1534
|
}
|
|
1411
1535
|
|
|
1412
1536
|
isInitialized() {
|
|
@@ -1468,6 +1592,75 @@ export class Device extends EventEmitter {
|
|
|
1468
1592
|
return res.message;
|
|
1469
1593
|
}
|
|
1470
1594
|
|
|
1595
|
+
beginProtocolV2UiInteraction() {
|
|
1596
|
+
if (!this.isProtocolV2()) return;
|
|
1597
|
+
this.protocolV2UiInteraction = {
|
|
1598
|
+
interactionId: `${this.instanceId}:${Date.now()}:${++this.protocolV2UiInteractionCounter}`,
|
|
1599
|
+
phaseCounter: 0,
|
|
1600
|
+
sequence: 0,
|
|
1601
|
+
opened: false,
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
createProtocolV2UiPhaseMetadata(
|
|
1606
|
+
phase: HardwareUiInteractionMeta['phase'],
|
|
1607
|
+
transition: HardwareUiInteractionMeta['transition'],
|
|
1608
|
+
options?: {
|
|
1609
|
+
phaseId?: string;
|
|
1610
|
+
outcome?: HardwareUiInteractionMeta['outcome'];
|
|
1611
|
+
}
|
|
1612
|
+
): HardwareUiInteractionMeta | undefined {
|
|
1613
|
+
if (!this.isProtocolV2()) return undefined;
|
|
1614
|
+
if (!this.protocolV2UiInteraction) this.beginProtocolV2UiInteraction();
|
|
1615
|
+
|
|
1616
|
+
const interaction = this.protocolV2UiInteraction;
|
|
1617
|
+
if (!interaction) return undefined;
|
|
1618
|
+
const phaseId =
|
|
1619
|
+
options?.phaseId ?? `${interaction.interactionId}:phase-${++interaction.phaseCounter}`;
|
|
1620
|
+
interaction.opened = true;
|
|
1621
|
+
interaction.sequence += 1;
|
|
1622
|
+
|
|
1623
|
+
return {
|
|
1624
|
+
interactionId: interaction.interactionId,
|
|
1625
|
+
phaseId,
|
|
1626
|
+
sequence: interaction.sequence,
|
|
1627
|
+
phase,
|
|
1628
|
+
transition,
|
|
1629
|
+
...(options?.outcome ? { outcome: options.outcome } : {}),
|
|
1630
|
+
protocol: 'V2',
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
completeProtocolV2UiPhase(
|
|
1635
|
+
phase: HardwareUiInteractionMeta,
|
|
1636
|
+
outcome: HardwareUiInteractionMeta['outcome'] = 'succeeded'
|
|
1637
|
+
) {
|
|
1638
|
+
return this.createProtocolV2UiPhaseMetadata(phase.phase, 'complete', {
|
|
1639
|
+
phaseId: phase.phaseId,
|
|
1640
|
+
outcome,
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
finishProtocolV2UiInteraction(outcome: HardwareUiInteractionMeta['outcome'] = 'succeeded') {
|
|
1645
|
+
const interaction = this.protocolV2UiInteraction;
|
|
1646
|
+
if (!interaction?.opened) {
|
|
1647
|
+
this.protocolV2UiInteraction = undefined;
|
|
1648
|
+
return undefined;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
const phaseId = `${interaction.interactionId}:phase-${Math.max(interaction.phaseCounter, 1)}`;
|
|
1652
|
+
const metadata = this.createProtocolV2UiPhaseMetadata('processing', 'finish', {
|
|
1653
|
+
phaseId,
|
|
1654
|
+
outcome,
|
|
1655
|
+
});
|
|
1656
|
+
this.protocolV2UiInteraction = undefined;
|
|
1657
|
+
return metadata;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
hasOpenProtocolV2UiInteraction() {
|
|
1661
|
+
return this.protocolV2UiInteraction?.opened === true;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1471
1664
|
supportUnlockVersionRange(): DeviceFirmwareRange {
|
|
1472
1665
|
// This range applies to Protocol V1 Pro devices; Pro2 has a dedicated unlock flow.
|
|
1473
1666
|
return {
|
|
@@ -1477,10 +1670,33 @@ export class Device extends EventEmitter {
|
|
|
1477
1670
|
};
|
|
1478
1671
|
}
|
|
1479
1672
|
|
|
1480
|
-
async unlockDevice(
|
|
1673
|
+
async unlockDevice(
|
|
1674
|
+
pinType?: DeviceSessionPinType,
|
|
1675
|
+
options?: ProtocolV2UiEventMetadata & { emitUiEvent?: boolean }
|
|
1676
|
+
) {
|
|
1481
1677
|
if (this.isProtocolV2()) {
|
|
1678
|
+
const requestedPinType = pinType ?? DeviceSessionPinType.Main;
|
|
1679
|
+
const interaction =
|
|
1680
|
+
options?.interaction ??
|
|
1681
|
+
(options?.emitUiEvent === false
|
|
1682
|
+
? undefined
|
|
1683
|
+
: this.createProtocolV2UiPhaseMetadata('pin', 'start'));
|
|
1684
|
+
if (options?.emitUiEvent !== false) {
|
|
1685
|
+
this.emit(DEVICE.PIN_ON_DEVICE, this, requestedPinType, {
|
|
1686
|
+
source: options?.source ?? 'unlock-coordinator',
|
|
1687
|
+
reason: options?.reason ?? 'device-unlock',
|
|
1688
|
+
deviceOnly: options?.deviceOnly ?? true,
|
|
1689
|
+
completion: options?.completion,
|
|
1690
|
+
method: options?.method,
|
|
1691
|
+
page: options?.page,
|
|
1692
|
+
operation: options?.operation,
|
|
1693
|
+
interaction: options?.interaction ?? interaction,
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1482
1696
|
try {
|
|
1483
|
-
await this.commands.typedCall('DeviceSessionAskPin', 'Success', {
|
|
1697
|
+
await this.commands.typedCall('DeviceSessionAskPin', 'Success', {
|
|
1698
|
+
type: requestedPinType,
|
|
1699
|
+
});
|
|
1484
1700
|
} catch (error) {
|
|
1485
1701
|
const errorText =
|
|
1486
1702
|
error instanceof Error
|
|
@@ -1492,6 +1708,11 @@ export class Device extends EventEmitter {
|
|
|
1492
1708
|
throw error;
|
|
1493
1709
|
}
|
|
1494
1710
|
|
|
1711
|
+
const completion = interaction ? this.completeProtocolV2UiPhase(interaction) : undefined;
|
|
1712
|
+
if (completion) {
|
|
1713
|
+
this.emit(DEVICE.PIN_ON_DEVICE_COMPLETE, this, completion);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1495
1716
|
const status = await requestProtocolV2DeviceStatus({ commands: this.commands });
|
|
1496
1717
|
return this.updateProtocolV2Status(status);
|
|
1497
1718
|
}
|
|
@@ -1553,7 +1774,8 @@ export class Device extends EventEmitter {
|
|
|
1553
1774
|
async checkPassphraseStateSafety(
|
|
1554
1775
|
passphraseState?: string,
|
|
1555
1776
|
useEmptyPassphrase?: boolean,
|
|
1556
|
-
skipPassphraseCheck?: boolean
|
|
1777
|
+
skipPassphraseCheck?: boolean,
|
|
1778
|
+
deriveCardano?: boolean
|
|
1557
1779
|
) {
|
|
1558
1780
|
if (this.isUnacquired()) return false;
|
|
1559
1781
|
|
|
@@ -1562,6 +1784,7 @@ export class Device extends EventEmitter {
|
|
|
1562
1784
|
await getPassphraseStateWithRefreshDeviceInfo(this, {
|
|
1563
1785
|
expectPassphraseState: expectedPassphraseState,
|
|
1564
1786
|
onlyMainPin: useEmptyPassphrase,
|
|
1787
|
+
deriveCardano,
|
|
1565
1788
|
});
|
|
1566
1789
|
|
|
1567
1790
|
// Main wallet and unlock Attach Pin, throw safe error
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type Messages,
|
|
7
7
|
type Transport,
|
|
8
8
|
type TransportCallOptions,
|
|
9
|
+
getSafeTransportLogPayload,
|
|
9
10
|
} from '@onekeyfe/hd-transport';
|
|
10
11
|
|
|
11
12
|
import TransportManager from '../data-manager/TransportManager';
|
|
@@ -282,7 +283,11 @@ export class DeviceCommands {
|
|
|
282
283
|
): Promise<DefaultMessageResponse> {
|
|
283
284
|
const shouldReduceDebug = shouldReduceDebugForCall(type);
|
|
284
285
|
if (!shouldReduceDebug) {
|
|
285
|
-
Log.debug(
|
|
286
|
+
Log.debug(
|
|
287
|
+
'[DeviceCommands] [call] Sending',
|
|
288
|
+
type,
|
|
289
|
+
getSafeTransportLogPayload(msg ?? {}, type)
|
|
290
|
+
);
|
|
286
291
|
}
|
|
287
292
|
|
|
288
293
|
try {
|
|
@@ -290,13 +295,18 @@ export class DeviceCommands {
|
|
|
290
295
|
this.callPromise = promise;
|
|
291
296
|
const res = await promise;
|
|
292
297
|
if (!shouldReduceDebug) {
|
|
293
|
-
LogCore.debug(
|
|
298
|
+
LogCore.debug(
|
|
299
|
+
'[DeviceCommands] [call] Received',
|
|
300
|
+
res.type,
|
|
301
|
+
getSafeTransportLogPayload(res.message, res.type)
|
|
302
|
+
);
|
|
294
303
|
}
|
|
295
304
|
return res;
|
|
296
305
|
} catch (error) {
|
|
297
306
|
LogCore.debug('[DeviceCommands] [call] Received error', {
|
|
298
307
|
request: type,
|
|
299
308
|
errorCode: error?.errorCode,
|
|
309
|
+
response: getSafeTransportLogPayload(error?.response?.data, type),
|
|
300
310
|
});
|
|
301
311
|
if (error.errorCode === HardwareErrorCode.BleDeviceBondError) {
|
|
302
312
|
return {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export class DeviceWalletSessionStore {
|
|
2
|
-
private static readonly MAX_SESSIONS_PER_DEVICE = 3;
|
|
3
|
-
|
|
4
2
|
private readonly walletSessions = new Map<string, Map<string, string>>();
|
|
5
3
|
|
|
6
4
|
private readonly standardWalletSessions = new Map<
|
|
@@ -22,18 +20,6 @@ export class DeviceWalletSessionStore {
|
|
|
22
20
|
deviceSessions = new Map<string, string>();
|
|
23
21
|
this.walletSessions.set(deviceKey, deviceSessions);
|
|
24
22
|
}
|
|
25
|
-
if (
|
|
26
|
-
!deviceSessions.has(passphraseState) &&
|
|
27
|
-
deviceSessions.size >= DeviceWalletSessionStore.MAX_SESSIONS_PER_DEVICE
|
|
28
|
-
) {
|
|
29
|
-
const oldestPassphraseState = deviceSessions.keys().next().value as string | undefined;
|
|
30
|
-
if (oldestPassphraseState) {
|
|
31
|
-
deviceSessions.delete(oldestPassphraseState);
|
|
32
|
-
if (this.standardWalletSessions.get(deviceKey)?.passphraseState === oldestPassphraseState) {
|
|
33
|
-
this.standardWalletSessions.delete(deviceKey);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
23
|
deviceSessions.set(passphraseState, sessionId);
|
|
38
24
|
}
|
|
39
25
|
|
|
@@ -260,7 +260,7 @@ export const buildProtocolV2FeaturesPayload = ({
|
|
|
260
260
|
const attachToPinEnabled = status?.attach_to_pin_enabled ?? cached?.attachToPinEnabled ?? null;
|
|
261
261
|
const unlockedAttachPin =
|
|
262
262
|
status?.unlocked_by_attach_to_pin ?? cached?.unlockedAttachPin ?? undefined;
|
|
263
|
-
const bootloaderMode = runtimeMode === 'bootloader';
|
|
263
|
+
const bootloaderMode = runtimeMode === 'bootloader' || runtimeMode === 'romloader';
|
|
264
264
|
let mode: DeviceFeaturesMode = 'unknown';
|
|
265
265
|
if (runtimeMode) {
|
|
266
266
|
mode = runtimeMode;
|
package/src/events/device.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MessageFactoryFn } from './utils';
|
|
2
|
+
import type { HardwareUiInteractionMeta } from './ui-request';
|
|
2
3
|
import type { PROTO } from '../constants';
|
|
3
4
|
import type {
|
|
4
5
|
KnownDevice as Device,
|
|
@@ -26,6 +27,8 @@ export const DEVICE = {
|
|
|
26
27
|
// onekey-transport events in protobuf format
|
|
27
28
|
BUTTON: 'button',
|
|
28
29
|
PIN: 'pin',
|
|
30
|
+
PIN_ON_DEVICE: 'pin_on_device',
|
|
31
|
+
PIN_ON_DEVICE_COMPLETE: 'pin_on_device_complete',
|
|
29
32
|
PASSPHRASE: 'passphrase',
|
|
30
33
|
PASSPHRASE_ON_DEVICE: 'passphrase_on_device',
|
|
31
34
|
ATTACH_PIN_ON_DEVICE: 'attach_pin_on_device',
|
|
@@ -58,6 +61,7 @@ export type PassphraseRequestPayload = {
|
|
|
58
61
|
source?: 'wallet-session-coordinator';
|
|
59
62
|
reason?: 'open-wallet' | 'session-recovery';
|
|
60
63
|
expectedPassphraseState?: string;
|
|
64
|
+
interaction?: HardwareUiInteractionMeta;
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
export interface DeviceButtonRequest {
|
package/src/events/ui-request.ts
CHANGED
|
@@ -56,6 +56,16 @@ export type ProtocolV2UiEventSource =
|
|
|
56
56
|
|
|
57
57
|
export type ProtocolV2UiCompletion = 'page-accepted' | 'operation-completed';
|
|
58
58
|
|
|
59
|
+
export type HardwareUiInteractionMeta = {
|
|
60
|
+
interactionId: string;
|
|
61
|
+
phaseId: string;
|
|
62
|
+
sequence: number;
|
|
63
|
+
phase: 'pin' | 'passphrase' | 'passphrase-on-device' | 'button' | 'processing';
|
|
64
|
+
transition: 'start' | 'complete' | 'finish';
|
|
65
|
+
outcome?: 'submitted' | 'succeeded' | 'failed' | 'cancelled' | 'disconnected';
|
|
66
|
+
protocol: 'V2';
|
|
67
|
+
};
|
|
68
|
+
|
|
59
69
|
export type ProtocolV2UiEventMetadata = {
|
|
60
70
|
source?: ProtocolV2UiEventSource;
|
|
61
71
|
reason?: string;
|
|
@@ -64,12 +74,29 @@ export type ProtocolV2UiEventMetadata = {
|
|
|
64
74
|
method?: string;
|
|
65
75
|
page?: string | number;
|
|
66
76
|
operation?: string;
|
|
77
|
+
interaction?: HardwareUiInteractionMeta;
|
|
67
78
|
};
|
|
68
79
|
|
|
80
|
+
export type UiRequestWindowClose =
|
|
81
|
+
| {
|
|
82
|
+
type: typeof UI_REQUEST.CLOSE_UI_WINDOW;
|
|
83
|
+
payload: HardwareUiInteractionMeta;
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
86
|
+
type: typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW;
|
|
87
|
+
payload: HardwareUiInteractionMeta;
|
|
88
|
+
}
|
|
89
|
+
| {
|
|
90
|
+
type: typeof UI_REQUEST.CLOSE_UI_WINDOW;
|
|
91
|
+
payload?: undefined;
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
type: typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW;
|
|
95
|
+
payload?: undefined;
|
|
96
|
+
};
|
|
97
|
+
|
|
69
98
|
export interface UiRequestWithoutPayload {
|
|
70
99
|
type:
|
|
71
|
-
| typeof UI_REQUEST.CLOSE_UI_WINDOW
|
|
72
|
-
| typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW
|
|
73
100
|
| typeof UI_REQUEST.BLUETOOTH_PERMISSION
|
|
74
101
|
| typeof UI_REQUEST.BLUETOOTH_UNSUPPORTED
|
|
75
102
|
| typeof UI_REQUEST.BLUETOOTH_POWERED_OFF
|
|
@@ -111,6 +138,7 @@ export interface UiRequestPassphrase {
|
|
|
111
138
|
source?: 'wallet-session-coordinator';
|
|
112
139
|
reason?: 'open-wallet' | 'session-recovery';
|
|
113
140
|
expectedPassphraseState?: string;
|
|
141
|
+
interaction?: HardwareUiInteractionMeta;
|
|
114
142
|
};
|
|
115
143
|
}
|
|
116
144
|
|
|
@@ -121,6 +149,7 @@ export interface UiRequestPassphraseOnDevice {
|
|
|
121
149
|
passphraseState?: string;
|
|
122
150
|
source?: 'wallet-session-coordinator';
|
|
123
151
|
reason?: 'open-wallet' | 'session-recovery';
|
|
152
|
+
interaction?: HardwareUiInteractionMeta;
|
|
124
153
|
};
|
|
125
154
|
}
|
|
126
155
|
|
|
@@ -191,6 +220,7 @@ export interface PreviousAddressResult {
|
|
|
191
220
|
|
|
192
221
|
export type UiEvent =
|
|
193
222
|
| UiRequestWithoutPayload
|
|
223
|
+
| UiRequestWindowClose
|
|
194
224
|
| UiRequestDeviceAction
|
|
195
225
|
| UiRequestButton
|
|
196
226
|
| UiRequestPassphraseOnDevice
|