@onekeyfe/hwk-trezor-adapter 1.2.8 → 1.2.10-alpha.1
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/dist/index.d.mts +70 -25
- package/dist/index.d.ts +70 -25
- package/dist/index.js +1205 -250
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1192 -237
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -3,14 +3,33 @@ import {
|
|
|
3
3
|
DEVICE,
|
|
4
4
|
DeviceJobQueue,
|
|
5
5
|
HardwareErrorCode,
|
|
6
|
+
OperationRegistry,
|
|
7
|
+
SDK,
|
|
6
8
|
TypedEventEmitter,
|
|
7
9
|
UI_REQUEST,
|
|
8
10
|
UI_RESPONSE,
|
|
9
11
|
UiRequestRegistry,
|
|
12
|
+
buildAllNetworkCancelledFailure,
|
|
13
|
+
canReplayHardwareMethodAfterTransportFailure,
|
|
14
|
+
createHwkError,
|
|
15
|
+
defaultOriginForCode,
|
|
16
|
+
emitDeviceBindingStatus,
|
|
10
17
|
failure,
|
|
18
|
+
isConnectionLost,
|
|
19
|
+
isHardwareOperationId,
|
|
20
|
+
isHwkRecoveryHint,
|
|
21
|
+
isUserRefusal,
|
|
22
|
+
isWalletSafetyFailure,
|
|
23
|
+
operationMayHaveCompletedParams,
|
|
24
|
+
pickConnectionContext,
|
|
11
25
|
rehydrateConnectorError,
|
|
26
|
+
requestBleDeviceSelection,
|
|
27
|
+
requestSaveDeviceBinding,
|
|
28
|
+
resolveHardwareOperationTarget,
|
|
12
29
|
runAllNetworkGetAddress,
|
|
13
|
-
success
|
|
30
|
+
success,
|
|
31
|
+
summarizeForLog,
|
|
32
|
+
toPhysicalDeviceSearchTarget
|
|
14
33
|
} from "@onekeyfe/hwk-adapter-core";
|
|
15
34
|
import { randomBytes } from "@noble/hashes/utils";
|
|
16
35
|
|
|
@@ -762,10 +781,34 @@ function debugLog(...args) {
|
|
|
762
781
|
emitLog("debug", ...args);
|
|
763
782
|
}
|
|
764
783
|
|
|
784
|
+
// src/utils/queueKey.ts
|
|
785
|
+
function trezorQueueKey({
|
|
786
|
+
operationId,
|
|
787
|
+
connectId
|
|
788
|
+
}) {
|
|
789
|
+
return operationId || connectId || "";
|
|
790
|
+
}
|
|
791
|
+
|
|
765
792
|
// src/adapter/TrezorAdapter.ts
|
|
766
793
|
var KNOWN_HARDWARE_ERROR_CODES = new Set(
|
|
767
794
|
Object.values(HardwareErrorCode).filter((value) => typeof value === "number")
|
|
768
795
|
);
|
|
796
|
+
var ALL_NETWORK_COMMON_PARAM_KEYS = [
|
|
797
|
+
"autoInstallApp",
|
|
798
|
+
"passphraseState",
|
|
799
|
+
"useEmptyPassphrase",
|
|
800
|
+
"operationId",
|
|
801
|
+
"knownConnections",
|
|
802
|
+
"extra",
|
|
803
|
+
"allowDeviceSelection",
|
|
804
|
+
"supportedTransports"
|
|
805
|
+
];
|
|
806
|
+
var ALL_NETWORK_ROUTING_PARAM_KEYS = [
|
|
807
|
+
"operationId",
|
|
808
|
+
"knownConnections",
|
|
809
|
+
"allowDeviceSelection",
|
|
810
|
+
"supportedTransports"
|
|
811
|
+
];
|
|
769
812
|
var TREZOR_BTC_NETWORK_COIN_MAP = {
|
|
770
813
|
btc: "Bitcoin",
|
|
771
814
|
bitcoin: "Bitcoin",
|
|
@@ -782,9 +825,24 @@ var TREZOR_BTC_NETWORK_COIN_MAP = {
|
|
|
782
825
|
dash: "Dash"
|
|
783
826
|
};
|
|
784
827
|
var _TrezorAdapter = class _TrezorAdapter {
|
|
785
|
-
constructor(connector) {
|
|
828
|
+
constructor(connector, options) {
|
|
786
829
|
this.vendor = "trezor";
|
|
830
|
+
// The connector has no protocol-level cancel: the adapter stops waiting and
|
|
831
|
+
// drops the late response, the device carries on.
|
|
832
|
+
this.cancelCapability = "stops-waiting";
|
|
787
833
|
this._emitter = new TypedEventEmitter();
|
|
834
|
+
this._operations = new OperationRegistry({
|
|
835
|
+
vendor: "trezor",
|
|
836
|
+
onEnded: (operation, reason) => {
|
|
837
|
+
this._emitter.emit(SDK.OPERATION_ENDED, {
|
|
838
|
+
type: SDK.OPERATION_ENDED,
|
|
839
|
+
payload: { operationId: operation.operationId, reason }
|
|
840
|
+
});
|
|
841
|
+
if (reason === "timeout") {
|
|
842
|
+
void this._releaseOperationConnection(operation).catch(() => void 0);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
788
846
|
this._devices = /* @__PURE__ */ new Map();
|
|
789
847
|
this._sessions = /* @__PURE__ */ new Map();
|
|
790
848
|
this._connectingPromises = /* @__PURE__ */ new Map();
|
|
@@ -793,12 +851,21 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
793
851
|
this._uiRegistry = new UiRequestRegistry();
|
|
794
852
|
this._passphraseRequestContextByConnectId = /* @__PURE__ */ new Map();
|
|
795
853
|
this._verifiedPassphraseSessionsByConnectId = /* @__PURE__ */ new Map();
|
|
854
|
+
/** Raw connector calls that outlive an aborted caller, keyed by session. */
|
|
855
|
+
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
856
|
+
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
857
|
+
this._resetPromise = null;
|
|
858
|
+
this._connectorTeardownTail = Promise.resolve();
|
|
859
|
+
this._pendingConnectorTeardowns = 0;
|
|
860
|
+
this._stateGeneration = 0;
|
|
861
|
+
this._knownDeviceConnections = /* @__PURE__ */ new Map();
|
|
796
862
|
this._onDeviceConnect = (data) => {
|
|
797
863
|
const device = this._connectorDeviceToDeviceInfo(data.device);
|
|
798
864
|
this._devices.set(device.connectId, device);
|
|
799
865
|
this._emitter.emit(DEVICE.CONNECT, { type: DEVICE.CONNECT, payload: device });
|
|
800
866
|
};
|
|
801
867
|
this._onDeviceDisconnect = (data) => {
|
|
868
|
+
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
802
869
|
this._sessions.delete(data.connectId);
|
|
803
870
|
this._verifiedPassphraseSessionsByConnectId.delete(data.connectId);
|
|
804
871
|
this._devices.delete(data.connectId);
|
|
@@ -818,28 +885,36 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
818
885
|
payload: { device }
|
|
819
886
|
});
|
|
820
887
|
};
|
|
888
|
+
// The one outlet for every connector-raised UI request (PIN, passphrase,
|
|
889
|
+
// button, THP pairing), so the operation is stamped here instead of at each
|
|
890
|
+
// connector call site.
|
|
821
891
|
this._onUiRequest = (data) => {
|
|
892
|
+
const operationId = this._activeOperationId();
|
|
893
|
+
const basePayload = data.payload && typeof data.payload === "object" ? data.payload : {};
|
|
894
|
+
const attribution = operationId === void 0 ? {} : { operationId };
|
|
822
895
|
if (data.type === UI_REQUEST.REQUEST_PASSPHRASE) {
|
|
823
|
-
const
|
|
824
|
-
const connectId = typeof payload.connectId === "string" ? payload.connectId : void 0;
|
|
896
|
+
const connectId = typeof basePayload.connectId === "string" ? basePayload.connectId : void 0;
|
|
825
897
|
const context = connectId ? this._passphraseRequestContextByConnectId.get(connectId) : void 0;
|
|
826
898
|
this._emitter.emit(
|
|
827
899
|
data.type,
|
|
828
900
|
{
|
|
829
901
|
...data,
|
|
830
902
|
payload: {
|
|
831
|
-
...
|
|
903
|
+
...basePayload,
|
|
832
904
|
...context?.passphraseState !== void 0 ? { passphraseState: context.passphraseState } : {},
|
|
833
|
-
...context?.useEmptyPassphrase !== void 0 ? { useEmptyPassphrase: context.useEmptyPassphrase } : {}
|
|
905
|
+
...context?.useEmptyPassphrase !== void 0 ? { useEmptyPassphrase: context.useEmptyPassphrase } : {},
|
|
906
|
+
...attribution
|
|
834
907
|
}
|
|
835
908
|
}
|
|
836
909
|
);
|
|
837
910
|
return;
|
|
838
911
|
}
|
|
839
|
-
|
|
912
|
+
const payload = operationId === void 0 ? data.payload : { ...basePayload, ...attribution };
|
|
913
|
+
this._emitter.emit(data.type, { ...data, payload });
|
|
840
914
|
};
|
|
841
915
|
this._onUiEvent = (data) => {
|
|
842
|
-
this.
|
|
916
|
+
const operationId = this._activeOperationId();
|
|
917
|
+
this._emitter.emit("ui-event", operationId ? { ...data, operationId } : data);
|
|
843
918
|
};
|
|
844
919
|
this._onThpCredentialsChanged = (data) => {
|
|
845
920
|
this._emitter.emit(DEVICE.TREZOR_THP_CREDENTIALS_CHANGED, {
|
|
@@ -848,6 +923,11 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
848
923
|
});
|
|
849
924
|
};
|
|
850
925
|
this._connector = connector;
|
|
926
|
+
for (const connection of options?.knownDeviceConnections ?? []) {
|
|
927
|
+
if (connection.deviceId) {
|
|
928
|
+
this._knownDeviceConnections.set(connection.deviceId, { ...connection });
|
|
929
|
+
}
|
|
930
|
+
}
|
|
851
931
|
this._jobQueue = new DeviceJobQueue();
|
|
852
932
|
this._registerConnectorEvents();
|
|
853
933
|
}
|
|
@@ -872,39 +952,55 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
872
952
|
const {
|
|
873
953
|
passphraseState,
|
|
874
954
|
autoInstallApp: _autoInstallApp,
|
|
955
|
+
operationId,
|
|
875
956
|
useEmptyPassphrase,
|
|
957
|
+
knownConnections,
|
|
958
|
+
extra,
|
|
959
|
+
allowDeviceSelection,
|
|
960
|
+
supportedTransports,
|
|
876
961
|
...rest
|
|
877
962
|
} = params;
|
|
878
963
|
return {
|
|
879
964
|
passphraseState: typeof passphraseState === "string" ? passphraseState : void 0,
|
|
880
965
|
useEmptyPassphrase: typeof useEmptyPassphrase === "boolean" ? useEmptyPassphrase : void 0,
|
|
966
|
+
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
967
|
+
connectionContext: pickConnectionContext({
|
|
968
|
+
knownConnections,
|
|
969
|
+
extra,
|
|
970
|
+
allowDeviceSelection,
|
|
971
|
+
supportedTransports
|
|
972
|
+
}),
|
|
881
973
|
rest
|
|
882
974
|
};
|
|
883
975
|
}
|
|
884
976
|
return { rest: params ?? {} };
|
|
885
977
|
}
|
|
886
978
|
static _withAllNetworkRequestCommonParams(item, commonParams) {
|
|
887
|
-
const
|
|
888
|
-
const passphraseState = commonParams?.passphraseState;
|
|
889
|
-
const useEmptyPassphrase = commonParams?.useEmptyPassphrase;
|
|
890
|
-
if (autoInstallApp === void 0 && passphraseState === void 0 && useEmptyPassphrase === void 0) {
|
|
891
|
-
return item;
|
|
892
|
-
}
|
|
979
|
+
const defined = ALL_NETWORK_COMMON_PARAM_KEYS.filter((key) => commonParams?.[key] !== void 0);
|
|
893
980
|
return {
|
|
894
981
|
...item,
|
|
895
|
-
...
|
|
896
|
-
...
|
|
897
|
-
...useEmptyPassphrase !== void 0 ? { useEmptyPassphrase } : {}
|
|
982
|
+
...Object.fromEntries(defined.map((key) => [key, commonParams?.[key]])),
|
|
983
|
+
...Object.fromEntries(ALL_NETWORK_ROUTING_PARAM_KEYS.map((key) => [key, commonParams?.[key]]))
|
|
898
984
|
};
|
|
899
985
|
}
|
|
900
986
|
static _normalizeLogKey(key) {
|
|
901
987
|
return key.replace(/[_-]/g, "").toLowerCase();
|
|
902
988
|
}
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
static _sanitizeForLog(value, methodName) {
|
|
906
|
-
|
|
907
|
-
|
|
989
|
+
// [REQ]/[RES] bodies: paths, chain ids and flags stay readable; transaction
|
|
990
|
+
// contents, keys and addresses become length + short hash; secrets are redacted.
|
|
991
|
+
static _sanitizeForLog(value, methodName, role = "params") {
|
|
992
|
+
return summarizeForLog(value, { method: methodName, role });
|
|
993
|
+
}
|
|
994
|
+
// A failure payload is an error record, so its message/error text stays readable.
|
|
995
|
+
static _responseForLog(response, methodName) {
|
|
996
|
+
if (response.success) {
|
|
997
|
+
return _TrezorAdapter._sanitizeForLog(response.payload, methodName, "result");
|
|
998
|
+
}
|
|
999
|
+
const out = summarizeForLog(
|
|
1000
|
+
{ error: response.payload },
|
|
1001
|
+
{ method: methodName, role: "result" }
|
|
1002
|
+
);
|
|
1003
|
+
return out && typeof out === "object" ? out.error : out;
|
|
908
1004
|
}
|
|
909
1005
|
static _redactForLog(value, seen) {
|
|
910
1006
|
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
@@ -925,14 +1021,17 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
925
1021
|
static _errorForLog(error) {
|
|
926
1022
|
if (!error || typeof error !== "object") return error;
|
|
927
1023
|
const typed = error;
|
|
928
|
-
return _TrezorAdapter.
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1024
|
+
return _TrezorAdapter._redactForLog(
|
|
1025
|
+
{
|
|
1026
|
+
name: typed.name,
|
|
1027
|
+
message: typed.message,
|
|
1028
|
+
code: typed.code,
|
|
1029
|
+
errorCode: typed.errorCode,
|
|
1030
|
+
statusCode: typed.statusCode,
|
|
1031
|
+
response: typed.response
|
|
1032
|
+
},
|
|
1033
|
+
/* @__PURE__ */ new WeakSet()
|
|
1034
|
+
);
|
|
936
1035
|
}
|
|
937
1036
|
_setPassphraseRequestContext(connectId, context) {
|
|
938
1037
|
const previous = this._passphraseRequestContextByConnectId.get(connectId);
|
|
@@ -1018,13 +1117,11 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1018
1117
|
* selected is gone (the device evicted it from its limited slot pool). The
|
|
1019
1118
|
* caller drops the stale cache entry and recreates from scratch.
|
|
1020
1119
|
*
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1023
|
-
* (which would silently re-prompt the passphrase).
|
|
1120
|
+
* Match only the protocol's explicit unallocated-channel code. Guessing from
|
|
1121
|
+
* an error message could replay a request after an unrelated business error.
|
|
1024
1122
|
*/
|
|
1025
1123
|
static _isStaleSessionError(code) {
|
|
1026
|
-
|
|
1027
|
-
return code === "ThpUnallocatedChannel" || /invalid.?session|unallocated.?session|session.?not.?found/i.test(code);
|
|
1124
|
+
return code === "ThpUnallocatedChannel";
|
|
1028
1125
|
}
|
|
1029
1126
|
static _errorCode(error) {
|
|
1030
1127
|
return error.code ?? _TrezorAdapter._trezorFailureMessage(error).code;
|
|
@@ -1038,13 +1135,13 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1038
1135
|
return protocolError.name === "TrezorProtocolError" && protocolError.code === "Malformed protocol format" && protocolError.message === "Malformed protocol format";
|
|
1039
1136
|
}
|
|
1040
1137
|
/**
|
|
1041
|
-
* Race a promise against an abort signal.
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
* uselessly in the background.
|
|
1138
|
+
* Race a promise against an abort signal. IConnector.call() can't actually
|
|
1139
|
+
* be cancelled at the protocol level, so the caller gets the abort while the
|
|
1140
|
+
* adapter keeps the transport exclusively reserved until the raw call drains.
|
|
1045
1141
|
*/
|
|
1046
1142
|
static _abortable(signal, promise) {
|
|
1047
1143
|
if (signal.aborted) {
|
|
1144
|
+
void promise.catch(() => void 0);
|
|
1048
1145
|
return Promise.reject(
|
|
1049
1146
|
signal.reason ?? new Error("Aborted")
|
|
1050
1147
|
);
|
|
@@ -1066,17 +1163,37 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1066
1163
|
);
|
|
1067
1164
|
});
|
|
1068
1165
|
}
|
|
1069
|
-
async _connectSession(connectId, signal) {
|
|
1166
|
+
async _connectSession(connectId, signal, stateGeneration, transportType) {
|
|
1070
1167
|
const connectOnce = async () => {
|
|
1071
|
-
|
|
1072
|
-
|
|
1168
|
+
if (signal?.aborted) {
|
|
1169
|
+
throw signal.reason ?? new Error("Aborted");
|
|
1170
|
+
}
|
|
1171
|
+
this._assertConnectorIdle("connectDevice");
|
|
1172
|
+
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1173
|
+
let connectPromise;
|
|
1073
1174
|
try {
|
|
1074
|
-
|
|
1175
|
+
connectPromise = transportType ? this._connector.connect(connectId, { transportType }) : this._connector.connect(connectId);
|
|
1075
1176
|
} catch (error) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1177
|
+
releaseOperation();
|
|
1178
|
+
throw error;
|
|
1179
|
+
}
|
|
1180
|
+
if (!signal) {
|
|
1181
|
+
try {
|
|
1182
|
+
return await connectPromise;
|
|
1183
|
+
} finally {
|
|
1184
|
+
releaseOperation();
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
try {
|
|
1188
|
+
const session2 = await _TrezorAdapter._abortable(signal, connectPromise);
|
|
1189
|
+
releaseOperation();
|
|
1190
|
+
return session2;
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
if (signal.aborted) {
|
|
1193
|
+
void connectPromise.then((orphan) => this._connector.disconnect(orphan.sessionId).catch(() => void 0)).catch(() => void 0).finally(releaseOperation);
|
|
1194
|
+
} else {
|
|
1195
|
+
releaseOperation();
|
|
1196
|
+
}
|
|
1080
1197
|
throw error;
|
|
1081
1198
|
}
|
|
1082
1199
|
};
|
|
@@ -1092,8 +1209,13 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1092
1209
|
});
|
|
1093
1210
|
session = await connectOnce();
|
|
1094
1211
|
}
|
|
1095
|
-
if (this._disconnectRequested.delete(connectId)) {
|
|
1096
|
-
|
|
1212
|
+
if (signal?.aborted || stateGeneration !== this._stateGeneration || this._disconnectRequested.delete(connectId)) {
|
|
1213
|
+
const releaseCleanup = this._retainConnectorOperation(`disconnect:${session.sessionId}`);
|
|
1214
|
+
try {
|
|
1215
|
+
await this._connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1216
|
+
} finally {
|
|
1217
|
+
releaseCleanup();
|
|
1218
|
+
}
|
|
1097
1219
|
throw Object.assign(new Error("Trezor connection aborted"), {
|
|
1098
1220
|
code: HardwareErrorCode.UserAborted
|
|
1099
1221
|
});
|
|
@@ -1109,14 +1231,9 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1109
1231
|
async init(_config) {
|
|
1110
1232
|
}
|
|
1111
1233
|
async dispose() {
|
|
1234
|
+
await this._resetStateAndDisconnectSessions();
|
|
1112
1235
|
this._unregisterConnectorEvents();
|
|
1113
|
-
this._uiRegistry.reset();
|
|
1114
|
-
this._jobQueue.clear();
|
|
1115
1236
|
this._connector.reset();
|
|
1116
|
-
this._devices.clear();
|
|
1117
|
-
this._sessions.clear();
|
|
1118
|
-
this._verifiedPassphraseSessionsByConnectId.clear();
|
|
1119
|
-
this._connectingPromises.clear();
|
|
1120
1237
|
this._emitter.removeAllListeners();
|
|
1121
1238
|
}
|
|
1122
1239
|
/**
|
|
@@ -1125,12 +1242,31 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1125
1242
|
* error flows. Aborts active jobs and releases UI waits.
|
|
1126
1243
|
*/
|
|
1127
1244
|
resetState() {
|
|
1245
|
+
void this._resetStateAndDisconnectSessions();
|
|
1246
|
+
}
|
|
1247
|
+
_resetStateAndDisconnectSessions() {
|
|
1248
|
+
if (this._resetPromise) return this._resetPromise;
|
|
1249
|
+
const sessionIds = new Set(this._sessions.values());
|
|
1250
|
+
this._stateGeneration += 1;
|
|
1251
|
+
this._operations.endAll("runtime-reset");
|
|
1128
1252
|
this._uiRegistry.reset();
|
|
1253
|
+
this._connector.uiResponse({ type: UI_RESPONSE.CANCEL });
|
|
1129
1254
|
this._jobQueue.clear();
|
|
1130
1255
|
this._devices.clear();
|
|
1131
1256
|
this._sessions.clear();
|
|
1132
1257
|
this._verifiedPassphraseSessionsByConnectId.clear();
|
|
1133
1258
|
this._connectingPromises.clear();
|
|
1259
|
+
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1260
|
+
for (const sessionId of sessionIds) {
|
|
1261
|
+
await this._connector.disconnect(sessionId).catch(() => void 0);
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
this._resetPromise = resetPromise;
|
|
1265
|
+
return resetPromise.finally(() => {
|
|
1266
|
+
if (this._resetPromise === resetPromise) {
|
|
1267
|
+
this._resetPromise = null;
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1134
1270
|
}
|
|
1135
1271
|
getAvailableTransports() {
|
|
1136
1272
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
@@ -1138,32 +1274,164 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1138
1274
|
async switchTransport(_type) {
|
|
1139
1275
|
}
|
|
1140
1276
|
async searchDevices(options) {
|
|
1141
|
-
await this._ensureDevicePermission();
|
|
1277
|
+
await this._ensureDevicePermission(void 0, void 0, options?.transportType);
|
|
1278
|
+
return this._searchDevices(options);
|
|
1279
|
+
}
|
|
1280
|
+
async _searchDevices(options, signal) {
|
|
1281
|
+
if (options?.transportType && !this._availableTransports().includes(options.transportType))
|
|
1282
|
+
return [];
|
|
1283
|
+
if (options?.resetSession) {
|
|
1284
|
+
await this._resetStateAndDisconnectSessions();
|
|
1285
|
+
} else {
|
|
1286
|
+
await this._connectorTeardownTail;
|
|
1287
|
+
}
|
|
1288
|
+
if (signal?.aborted) throw signal.reason;
|
|
1289
|
+
const stateGeneration = this._stateGeneration;
|
|
1142
1290
|
const scanned = await this._connector.searchDevices(
|
|
1143
|
-
options?.waitForAllTransports ? {
|
|
1291
|
+
options?.waitForAllTransports || options?.transportType ? {
|
|
1292
|
+
...options.waitForAllTransports ? { waitForAll: true } : {},
|
|
1293
|
+
...options.transportType ? { transportType: options.transportType } : {}
|
|
1294
|
+
} : void 0
|
|
1144
1295
|
);
|
|
1296
|
+
if (signal?.aborted) throw signal.reason;
|
|
1297
|
+
if (stateGeneration !== this._stateGeneration) {
|
|
1298
|
+
throw createHwkError({
|
|
1299
|
+
code: HardwareErrorCode.UserAborted,
|
|
1300
|
+
message: "Trezor discovery belongs to an ended operation"
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1145
1303
|
const scannedIds = new Set(scanned.map((d) => d.connectId));
|
|
1146
1304
|
for (const connectId of [...this._devices.keys()]) {
|
|
1147
|
-
if (!scannedIds.has(connectId) && !this._sessions.has(connectId)) {
|
|
1305
|
+
if ((!options?.transportType || this._devices.get(connectId)?.connectionType === options.transportType) && !scannedIds.has(connectId) && !this._sessions.has(connectId)) {
|
|
1148
1306
|
this._devices.delete(connectId);
|
|
1149
1307
|
}
|
|
1150
1308
|
}
|
|
1151
1309
|
for (const device of scanned) {
|
|
1152
1310
|
const info = this._connectorDeviceToDeviceInfo(device);
|
|
1153
|
-
this._devices.set(info.connectId, info);
|
|
1311
|
+
if (!this._sessions.has(info.connectId)) this._devices.set(info.connectId, info);
|
|
1154
1312
|
}
|
|
1155
|
-
return Array.from(this._devices.values())
|
|
1313
|
+
return Array.from(this._devices.values()).filter(
|
|
1314
|
+
(device) => !options?.transportType || device.connectionType === options.transportType
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
async searchDeviceTargets(options) {
|
|
1318
|
+
const devices = await this.searchDevices(options);
|
|
1319
|
+
return devices.map((device) => toPhysicalDeviceSearchTarget("trezor", device));
|
|
1156
1320
|
}
|
|
1157
|
-
async
|
|
1321
|
+
async bindBleDevice(params) {
|
|
1322
|
+
if (params.identity?.vendor !== "trezor" || !params.identity.value) {
|
|
1323
|
+
return failure(HardwareErrorCode.InvalidParams, "Trezor device identity is required");
|
|
1324
|
+
}
|
|
1325
|
+
const { value: deviceId } = params.identity;
|
|
1158
1326
|
try {
|
|
1159
|
-
await this.
|
|
1160
|
-
|
|
1161
|
-
|
|
1327
|
+
return await this._jobQueue.enqueue(
|
|
1328
|
+
deviceId,
|
|
1329
|
+
async (signal) => {
|
|
1330
|
+
const selected = await this._selectBleDeviceForBinding(
|
|
1331
|
+
deviceId,
|
|
1332
|
+
signal,
|
|
1333
|
+
{ extra: params.extra },
|
|
1334
|
+
"manual-rebind"
|
|
1335
|
+
);
|
|
1336
|
+
try {
|
|
1337
|
+
const persisted = await this._emitVerifiedBinding(
|
|
1338
|
+
deviceId,
|
|
1339
|
+
selected.connectId,
|
|
1340
|
+
selected.selectionRequestId,
|
|
1341
|
+
{ extra: params.extra },
|
|
1342
|
+
signal
|
|
1343
|
+
);
|
|
1344
|
+
if (!persisted) {
|
|
1345
|
+
throw createHwkError({
|
|
1346
|
+
code: HardwareErrorCode.UnknownError,
|
|
1347
|
+
message: "Bluetooth binding could not be saved",
|
|
1348
|
+
origin: "host"
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
this._rememberVerifiedConnection(deviceId, selected.connectId);
|
|
1352
|
+
return success(selected.connectId);
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
await this._releaseProvisionalConnection(selected.connectId, signal);
|
|
1355
|
+
throw error;
|
|
1356
|
+
}
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
label: "bindBleDevice",
|
|
1360
|
+
rejectIfBusy: true,
|
|
1361
|
+
busyError: _TrezorAdapter._createDeviceBusyError("bindBleDevice")
|
|
1362
|
+
}
|
|
1363
|
+
);
|
|
1162
1364
|
} catch (error) {
|
|
1163
1365
|
return this._errorToFailure(error);
|
|
1164
1366
|
}
|
|
1165
1367
|
}
|
|
1166
|
-
async
|
|
1368
|
+
async connectDevice(searchTargetId) {
|
|
1369
|
+
const scope = this._jobQueue.createCancelScope(searchTargetId);
|
|
1370
|
+
try {
|
|
1371
|
+
await _TrezorAdapter._abortable(
|
|
1372
|
+
scope.signal,
|
|
1373
|
+
this._ensureDevicePermission(
|
|
1374
|
+
searchTargetId,
|
|
1375
|
+
void 0,
|
|
1376
|
+
this._devices.get(searchTargetId)?.connectionType
|
|
1377
|
+
)
|
|
1378
|
+
);
|
|
1379
|
+
await this._ensureSession(searchTargetId, scope.signal);
|
|
1380
|
+
if (scope.signal.aborted) throw scope.signal.reason;
|
|
1381
|
+
const device = this._devices.get(searchTargetId);
|
|
1382
|
+
if (!device) {
|
|
1383
|
+
return failure(HardwareErrorCode.DeviceNotFound, "Trezor device not found after connect");
|
|
1384
|
+
}
|
|
1385
|
+
this._operations.endByConnectionKey(searchTargetId, "explicit");
|
|
1386
|
+
const operation = this._operations.create({
|
|
1387
|
+
searchTargetId,
|
|
1388
|
+
connectId: searchTargetId,
|
|
1389
|
+
device,
|
|
1390
|
+
connectionType: device.connectionType,
|
|
1391
|
+
connectionKeys: [this._sessions.get(searchTargetId) ?? ""]
|
|
1392
|
+
});
|
|
1393
|
+
return success(operation.operationId);
|
|
1394
|
+
} catch (error) {
|
|
1395
|
+
if (scope.signal.aborted) {
|
|
1396
|
+
await this._waitForConnectorOperationsToDrain();
|
|
1397
|
+
return failure(HardwareErrorCode.UserAborted, "Trezor connection aborted");
|
|
1398
|
+
}
|
|
1399
|
+
return this._errorToFailure(error);
|
|
1400
|
+
} finally {
|
|
1401
|
+
scope.release();
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
async releaseOperation(operationId) {
|
|
1405
|
+
const operation = this._operations.findActive(operationId);
|
|
1406
|
+
if (operation) this._cancelOperationWork(operation);
|
|
1407
|
+
const endedOperation = this._operations.release(operationId);
|
|
1408
|
+
if (!endedOperation) return;
|
|
1409
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Ends a released operation's calls and prompts before teardown, which otherwise waits on a call
|
|
1413
|
+
* that waits on a prompt nobody will answer. Device I/O already on the wire still drains.
|
|
1414
|
+
*/
|
|
1415
|
+
_cancelOperationWork(operation) {
|
|
1416
|
+
const { operationId } = operation;
|
|
1417
|
+
const reason = createHwkError({
|
|
1418
|
+
code: HardwareErrorCode.OperationEnded,
|
|
1419
|
+
message: "Trezor operation was released",
|
|
1420
|
+
params: { operationId, reason: "explicit" }
|
|
1421
|
+
});
|
|
1422
|
+
const queueKey = trezorQueueKey({ operationId });
|
|
1423
|
+
const ownsRunningCall = this._jobQueue.getActiveJob()?.deviceId === queueKey;
|
|
1424
|
+
this._jobQueue.cancelActiveAndPending(queueKey, reason);
|
|
1425
|
+
if (!ownsRunningCall) return;
|
|
1426
|
+
this._uiRegistry.cancel(void 0, void 0, operationId);
|
|
1427
|
+
this._connector.uiResponse({ type: UI_RESPONSE.CANCEL });
|
|
1428
|
+
}
|
|
1429
|
+
async _releaseOperationConnection(operation) {
|
|
1430
|
+
const { connectId } = operation;
|
|
1431
|
+
const activeKey = this._jobQueue.getActiveJob()?.deviceId;
|
|
1432
|
+
if (activeKey !== void 0 && !isHardwareOperationId(activeKey) && (activeKey === connectId || activeKey === "" || activeKey === operation.device.deviceId)) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1167
1435
|
const connecting = this._connectingPromises.has(connectId);
|
|
1168
1436
|
this._connectingPromises.delete(connectId);
|
|
1169
1437
|
const sessionId = this._sessions.get(connectId);
|
|
@@ -1171,90 +1439,131 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1171
1439
|
if (connecting) this._disconnectRequested.add(connectId);
|
|
1172
1440
|
return;
|
|
1173
1441
|
}
|
|
1174
|
-
this.
|
|
1175
|
-
this._verifiedPassphraseSessionsByConnectId.delete(connectId);
|
|
1176
|
-
await this._connector.disconnect(sessionId);
|
|
1442
|
+
await this._disconnectSession(connectId, sessionId);
|
|
1177
1443
|
}
|
|
1178
|
-
async getDeviceInfo(
|
|
1444
|
+
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1445
|
+
let connectId;
|
|
1446
|
+
try {
|
|
1447
|
+
connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
return this._errorToFailure(error);
|
|
1450
|
+
}
|
|
1179
1451
|
const device = this._devices.get(connectId) ?? Array.from(this._devices.values()).find((item) => item.deviceId === deviceId);
|
|
1180
1452
|
if (!device) return failure(HardwareErrorCode.DeviceNotFound, "Trezor device not found");
|
|
1181
1453
|
return success(device);
|
|
1182
1454
|
}
|
|
1183
|
-
getSupportedChains() {
|
|
1184
|
-
return ["btc", "evm", "sol", "tron"];
|
|
1185
|
-
}
|
|
1186
1455
|
async allNetworkGetAddress(connectId, deviceId, params) {
|
|
1456
|
+
const target = resolveHardwareOperationTarget(connectId, params.operationId, "trezor");
|
|
1457
|
+
if (!target.success) return target;
|
|
1458
|
+
let effectiveTargetId = target.payload.targetId ?? "";
|
|
1459
|
+
let releaseOperationRetention;
|
|
1460
|
+
try {
|
|
1461
|
+
releaseOperationRetention = target.payload.operationId ? this._operations.retain(target.payload.operationId) : void 0;
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
return this._errorToFailure(error);
|
|
1464
|
+
}
|
|
1465
|
+
const cancelScope = this._jobQueue.createCancelScope(
|
|
1466
|
+
trezorQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
1467
|
+
);
|
|
1187
1468
|
let liveDeviceId = "";
|
|
1469
|
+
const bundleContext = {};
|
|
1188
1470
|
const topLevelFailureIndexes = /* @__PURE__ */ new Set();
|
|
1189
1471
|
const isSingleNetworkBundle = params.bundle.every(
|
|
1190
1472
|
(item) => item.network === params.bundle[0]?.network
|
|
1191
1473
|
);
|
|
1192
|
-
const getLiveDeviceId = async () => {
|
|
1474
|
+
const getLiveDeviceId = async (expectedDeviceId) => {
|
|
1193
1475
|
if (liveDeviceId) return success(liveDeviceId);
|
|
1194
|
-
const deviceIdResponse = await this._getTrezorDeviceId(
|
|
1476
|
+
const deviceIdResponse = await this._getTrezorDeviceId(
|
|
1477
|
+
effectiveTargetId,
|
|
1478
|
+
expectedDeviceId || deviceId,
|
|
1479
|
+
params,
|
|
1480
|
+
bundleContext
|
|
1481
|
+
);
|
|
1195
1482
|
if (deviceIdResponse.success) {
|
|
1196
1483
|
liveDeviceId = deviceIdResponse.payload;
|
|
1484
|
+
if (!target.payload.operationId) {
|
|
1485
|
+
effectiveTargetId = bundleContext.connection?.connectId ?? effectiveTargetId;
|
|
1486
|
+
}
|
|
1197
1487
|
}
|
|
1198
1488
|
return deviceIdResponse;
|
|
1199
1489
|
};
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const fingerprint = await getLiveDeviceId();
|
|
1209
|
-
if (!fingerprint.success) {
|
|
1490
|
+
try {
|
|
1491
|
+
return await runAllNetworkGetAddress({
|
|
1492
|
+
connectId: effectiveTargetId,
|
|
1493
|
+
deviceId,
|
|
1494
|
+
params,
|
|
1495
|
+
normalizeItem: _TrezorAdapter._normalizeAllNetworkItem,
|
|
1496
|
+
callItem: async ({ method, item, index }) => {
|
|
1497
|
+
if (cancelScope.signal.aborted) {
|
|
1210
1498
|
topLevelFailureIndexes.add(index);
|
|
1211
|
-
return
|
|
1499
|
+
return buildAllNetworkCancelledFailure(cancelScope.signal);
|
|
1212
1500
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1501
|
+
const expectedDeviceId = _TrezorAdapter._getItemDeviceId(item) ?? deviceId;
|
|
1502
|
+
if (expectedDeviceId) {
|
|
1503
|
+
const fingerprint = await getLiveDeviceId(expectedDeviceId);
|
|
1504
|
+
if (!fingerprint.success) {
|
|
1505
|
+
topLevelFailureIndexes.add(index);
|
|
1506
|
+
return fingerprint;
|
|
1507
|
+
}
|
|
1508
|
+
if (expectedDeviceId !== fingerprint.payload) {
|
|
1509
|
+
topLevelFailureIndexes.add(index);
|
|
1510
|
+
return failure(
|
|
1511
|
+
HardwareErrorCode.DeviceMismatch,
|
|
1512
|
+
`Wrong device: expected ${expectedDeviceId}, got ${fingerprint.payload}`,
|
|
1513
|
+
{ expected: expectedDeviceId, actual: fingerprint.payload }
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1220
1516
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1517
|
+
const callItem = _TrezorAdapter._withAllNetworkRequestCommonParams(item, params);
|
|
1518
|
+
return this._callAllNetworkMethod(effectiveTargetId, method, callItem, bundleContext);
|
|
1519
|
+
},
|
|
1520
|
+
attachIdentity: async ({ item, chain, payload, index }) => {
|
|
1521
|
+
if (!liveDeviceId) {
|
|
1522
|
+
if (cancelScope.signal.aborted) {
|
|
1523
|
+
topLevelFailureIndexes.add(index);
|
|
1524
|
+
const cancelled = buildAllNetworkCancelledFailure(cancelScope.signal);
|
|
1525
|
+
return { ...item, success: false, payload: cancelled.payload };
|
|
1526
|
+
}
|
|
1527
|
+
const fingerprint = await getLiveDeviceId(deviceId);
|
|
1528
|
+
if (!fingerprint.success) {
|
|
1529
|
+
return { ...item, success: false, payload: fingerprint.payload };
|
|
1530
|
+
}
|
|
1230
1531
|
}
|
|
1532
|
+
return {
|
|
1533
|
+
...item,
|
|
1534
|
+
success: true,
|
|
1535
|
+
payload: {
|
|
1536
|
+
...payload,
|
|
1537
|
+
deviceIdentity: {
|
|
1538
|
+
vendor: "trezor",
|
|
1539
|
+
type: "deviceId",
|
|
1540
|
+
value: liveDeviceId
|
|
1541
|
+
},
|
|
1542
|
+
chainFingerprint: liveDeviceId,
|
|
1543
|
+
chainFingerprintChain: chain
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
},
|
|
1547
|
+
// One named boolean per abort reason.
|
|
1548
|
+
shouldAbortBundle: (response, { index }) => {
|
|
1549
|
+
const isSessionLevelFailure = topLevelFailureIndexes.has(index);
|
|
1550
|
+
const isWholeChainForbidden = isSingleNetworkBundle && response.payload?.code === HardwareErrorCode.DevicePathForbidden;
|
|
1551
|
+
const { code } = response.payload ?? {};
|
|
1552
|
+
return isSessionLevelFailure || isWholeChainForbidden || isWalletSafetyFailure(code) || isUserRefusal(code) || isConnectionLost(code);
|
|
1231
1553
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
deviceIdentity: {
|
|
1238
|
-
vendor: "trezor",
|
|
1239
|
-
type: "deviceId",
|
|
1240
|
-
value: liveDeviceId
|
|
1241
|
-
},
|
|
1242
|
-
chainFingerprint: liveDeviceId,
|
|
1243
|
-
chainFingerprintChain: chain
|
|
1244
|
-
}
|
|
1245
|
-
};
|
|
1246
|
-
},
|
|
1247
|
-
// One named boolean per abort reason.
|
|
1248
|
-
shouldAbortBundle: (response, { index }) => {
|
|
1249
|
-
const isSessionLevelFailure = topLevelFailureIndexes.has(index);
|
|
1250
|
-
const isWholeChainForbidden = isSingleNetworkBundle && response.payload?.code === HardwareErrorCode.DevicePathForbidden;
|
|
1251
|
-
const isPassphrasePolicyFailure = response.payload?.code === HardwareErrorCode.PassphraseAlwaysOnDevice;
|
|
1252
|
-
return isSessionLevelFailure || isWholeChainForbidden || isPassphrasePolicyFailure;
|
|
1253
|
-
}
|
|
1254
|
-
});
|
|
1554
|
+
});
|
|
1555
|
+
} finally {
|
|
1556
|
+
cancelScope.release();
|
|
1557
|
+
releaseOperationRetention?.();
|
|
1558
|
+
}
|
|
1255
1559
|
}
|
|
1256
|
-
getFeatures(connectId) {
|
|
1257
|
-
return this._callDeviceManagerMethod(
|
|
1560
|
+
getFeatures(connectId, operationContext) {
|
|
1561
|
+
return this._callDeviceManagerMethod(
|
|
1562
|
+
"getFeatures",
|
|
1563
|
+
connectId,
|
|
1564
|
+
{},
|
|
1565
|
+
operationContext
|
|
1566
|
+
);
|
|
1258
1567
|
}
|
|
1259
1568
|
/**
|
|
1260
1569
|
* Device authenticity attestation. Sends a host-generated challenge, has the
|
|
@@ -1314,28 +1623,69 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1314
1623
|
}
|
|
1315
1624
|
});
|
|
1316
1625
|
}
|
|
1317
|
-
deviceSettings(connectId, params) {
|
|
1626
|
+
deviceSettings(connectId, params, operationContext) {
|
|
1318
1627
|
return this._callDeviceManagerMethod(
|
|
1319
1628
|
"deviceSettings",
|
|
1320
1629
|
connectId,
|
|
1321
|
-
params
|
|
1630
|
+
params,
|
|
1631
|
+
operationContext
|
|
1322
1632
|
);
|
|
1323
1633
|
}
|
|
1324
|
-
setBrightness(connectId, params = {}) {
|
|
1634
|
+
setBrightness(connectId, params = {}, operationContext) {
|
|
1325
1635
|
return this._callDeviceManagerMethod(
|
|
1326
1636
|
"setBrightness",
|
|
1327
1637
|
connectId,
|
|
1328
|
-
params
|
|
1638
|
+
params,
|
|
1639
|
+
operationContext
|
|
1329
1640
|
);
|
|
1330
1641
|
}
|
|
1331
|
-
changePin(connectId, params = {}) {
|
|
1332
|
-
return this._callDeviceManagerMethod(
|
|
1642
|
+
changePin(connectId, params = {}, operationContext) {
|
|
1643
|
+
return this._callDeviceManagerMethod(
|
|
1644
|
+
"changePin",
|
|
1645
|
+
connectId,
|
|
1646
|
+
params,
|
|
1647
|
+
operationContext
|
|
1648
|
+
);
|
|
1333
1649
|
}
|
|
1334
|
-
wipeDevice(connectId) {
|
|
1335
|
-
return this._callDeviceManagerMethod(
|
|
1650
|
+
wipeDevice(connectId, operationContext) {
|
|
1651
|
+
return this._callDeviceManagerMethod(
|
|
1652
|
+
"wipeDevice",
|
|
1653
|
+
connectId,
|
|
1654
|
+
{},
|
|
1655
|
+
operationContext
|
|
1656
|
+
);
|
|
1336
1657
|
}
|
|
1337
|
-
async _getTrezorDeviceId(connectId) {
|
|
1338
|
-
const
|
|
1658
|
+
async _getTrezorDeviceId(connectId, expectedDeviceId, commonParams, bundleContext) {
|
|
1659
|
+
const hasConnectionContext = commonParams?.knownConnections !== void 0 || commonParams?.extra !== void 0;
|
|
1660
|
+
const features = bundleContext || hasConnectionContext ? await this._callMethod(
|
|
1661
|
+
"getFeatures",
|
|
1662
|
+
connectId,
|
|
1663
|
+
hasConnectionContext || !connectId ? expectedDeviceId : void 0,
|
|
1664
|
+
{
|
|
1665
|
+
// Routing limits always apply: the connection found here is reused by the whole
|
|
1666
|
+
// bundle, so dropping them would let a bundle open a transport the host excluded.
|
|
1667
|
+
operationId: commonParams?.operationId,
|
|
1668
|
+
knownConnections: commonParams?.knownConnections,
|
|
1669
|
+
extra: commonParams?.extra,
|
|
1670
|
+
allowDeviceSelection: commonParams?.allowDeviceSelection,
|
|
1671
|
+
supportedTransports: commonParams?.supportedTransports,
|
|
1672
|
+
...hasConnectionContext ? {
|
|
1673
|
+
passphraseState: commonParams.passphraseState,
|
|
1674
|
+
useEmptyPassphrase: commonParams.useEmptyPassphrase
|
|
1675
|
+
} : {}
|
|
1676
|
+
},
|
|
1677
|
+
false,
|
|
1678
|
+
bundleContext
|
|
1679
|
+
) : await this.getFeatures(
|
|
1680
|
+
connectId,
|
|
1681
|
+
!connectId && expectedDeviceId ? {
|
|
1682
|
+
expectedDeviceIdentity: {
|
|
1683
|
+
vendor: "trezor",
|
|
1684
|
+
type: "deviceId",
|
|
1685
|
+
value: expectedDeviceId
|
|
1686
|
+
}
|
|
1687
|
+
} : void 0
|
|
1688
|
+
);
|
|
1339
1689
|
if (!features.success) return features;
|
|
1340
1690
|
const liveDeviceId = features.payload.device_id;
|
|
1341
1691
|
if (typeof liveDeviceId !== "string" || liveDeviceId.length === 0) {
|
|
@@ -1344,23 +1694,63 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1344
1694
|
return success(liveDeviceId);
|
|
1345
1695
|
}
|
|
1346
1696
|
cancel(connectId) {
|
|
1697
|
+
if (isHardwareOperationId(connectId)) {
|
|
1698
|
+
try {
|
|
1699
|
+
this._operations.resolve(connectId);
|
|
1700
|
+
} catch {
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1347
1704
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1348
1705
|
code: HardwareErrorCode.UserAborted
|
|
1349
1706
|
});
|
|
1350
|
-
this.
|
|
1351
|
-
|
|
1352
|
-
if (
|
|
1353
|
-
|
|
1354
|
-
|
|
1707
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1708
|
+
const targetId = connectId ?? activeJobId;
|
|
1709
|
+
if (targetId) {
|
|
1710
|
+
let resolvedConnectId = targetId;
|
|
1711
|
+
let operationId;
|
|
1712
|
+
if (isHardwareOperationId(targetId)) {
|
|
1713
|
+
try {
|
|
1714
|
+
operationId = targetId;
|
|
1715
|
+
resolvedConnectId = this._operations.resolve(targetId).connectId;
|
|
1716
|
+
} catch {
|
|
1717
|
+
resolvedConnectId = "";
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1721
|
+
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1722
|
+
if (!connectId) {
|
|
1723
|
+
this._uiRegistry.cancel();
|
|
1724
|
+
} else if (pendingOperationId) {
|
|
1725
|
+
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1726
|
+
}
|
|
1727
|
+
const queueKeys = /* @__PURE__ */ new Set([trezorQueueKey({ connectId: targetId })]);
|
|
1728
|
+
if (pendingOperationId) queueKeys.add(trezorQueueKey({ operationId: pendingOperationId }));
|
|
1729
|
+
const ownsConnectorInteraction = activeJobId !== void 0 ? queueKeys.has(activeJobId) || pendingOperationId !== void 0 && this._activeOperationId() === pendingOperationId : this._connectingPromises.size === 1 && this._connectingPromises.has(resolvedConnectId);
|
|
1730
|
+
if (connectId === void 0 || ownsConnectorInteraction) {
|
|
1731
|
+
this._connector.uiResponse({ type: UI_RESPONSE.CANCEL });
|
|
1732
|
+
}
|
|
1733
|
+
for (const key of queueKeys) {
|
|
1734
|
+
this._jobQueue.cancelActiveAndPending(key, userAbortReason);
|
|
1735
|
+
}
|
|
1736
|
+
if (resolvedConnectId && (activeJobId === void 0 || ownsConnectorInteraction)) {
|
|
1737
|
+
void this._connector.cancel(this._sessions.get(resolvedConnectId) ?? resolvedConnectId);
|
|
1738
|
+
}
|
|
1355
1739
|
return;
|
|
1356
1740
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1741
|
+
this._uiRegistry.cancel();
|
|
1742
|
+
this._connector.uiResponse({ type: UI_RESPONSE.CANCEL });
|
|
1743
|
+
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1360
1744
|
for (const sessionId of this._sessions.values()) {
|
|
1361
1745
|
void this._connector.cancel(sessionId);
|
|
1362
1746
|
}
|
|
1363
1747
|
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Only the operation explicitly supplied to the running job owns its UI requests.
|
|
1750
|
+
*/
|
|
1751
|
+
_activeOperationId() {
|
|
1752
|
+
return this._operations.findOperationIdByJobKey(this._jobQueue.getActiveJob()?.deviceId);
|
|
1753
|
+
}
|
|
1364
1754
|
/**
|
|
1365
1755
|
* Routes to BOTH the adapter's local UI registry (preemption decisions,
|
|
1366
1756
|
* future SELECT_DEVICE / DEVICE_PERMISSION) AND the connector's registry
|
|
@@ -1369,6 +1759,7 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1369
1759
|
*/
|
|
1370
1760
|
uiResponse(response) {
|
|
1371
1761
|
this._uiRegistry.resolve(response.type, response.payload);
|
|
1762
|
+
if (response.type === UI_RESPONSE.RECEIVE_SELECT_DEVICE) return;
|
|
1372
1763
|
this._connector.uiResponse(response);
|
|
1373
1764
|
}
|
|
1374
1765
|
/**
|
|
@@ -1380,8 +1771,8 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1380
1771
|
* Unlike Ledger (which hashes a per-chain address), it is chain-global, so
|
|
1381
1772
|
* `chain` is accepted for API parity but ignored.
|
|
1382
1773
|
*/
|
|
1383
|
-
async getChainFingerprint(connectId,
|
|
1384
|
-
return this._getTrezorDeviceId(connectId);
|
|
1774
|
+
async getChainFingerprint(connectId, deviceId, _chain) {
|
|
1775
|
+
return this._getTrezorDeviceId(connectId, deviceId);
|
|
1385
1776
|
}
|
|
1386
1777
|
/**
|
|
1387
1778
|
* Resolve the active passphrase wallet identity (`passphraseState`).
|
|
@@ -1391,17 +1782,87 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1391
1782
|
* can't interleave with a chain call on the same device (which would desync
|
|
1392
1783
|
* the THP session selection).
|
|
1393
1784
|
*/
|
|
1394
|
-
getPassphraseState(connectId, passphraseState) {
|
|
1395
|
-
|
|
1785
|
+
getPassphraseState(connectId, passphraseState, operationContext) {
|
|
1786
|
+
const expectedIdentity = operationContext?.expectedDeviceIdentity;
|
|
1787
|
+
if (expectedIdentity && (expectedIdentity.vendor !== "trezor" || expectedIdentity.type !== "deviceId")) {
|
|
1788
|
+
return Promise.resolve(
|
|
1789
|
+
failure(HardwareErrorCode.InvalidParams, "Trezor device identity is required")
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
const target = resolveHardwareOperationTarget(
|
|
1396
1793
|
connectId,
|
|
1794
|
+
operationContext?.operationId,
|
|
1795
|
+
"trezor"
|
|
1796
|
+
);
|
|
1797
|
+
if (!target.success) return Promise.resolve(target);
|
|
1798
|
+
const { operationId } = target.payload;
|
|
1799
|
+
let resolvedConnectId = target.payload.targetId ?? "";
|
|
1800
|
+
let releaseOperationRetention;
|
|
1801
|
+
if (operationId) {
|
|
1802
|
+
try {
|
|
1803
|
+
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1804
|
+
releaseOperationRetention = this._operations.retain(operationId);
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
return Promise.resolve(this._errorToFailure(error));
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return this._jobQueue.enqueue(
|
|
1810
|
+
trezorQueueKey({ operationId, connectId: resolvedConnectId }),
|
|
1397
1811
|
async (signal) => {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
});
|
|
1812
|
+
let selectionRequestId;
|
|
1813
|
+
let verified = false;
|
|
1401
1814
|
try {
|
|
1402
|
-
|
|
1815
|
+
if (!operationId && expectedIdentity?.value && (!connectId || operationContext?.knownConnections !== void 0)) {
|
|
1816
|
+
const resolved = await this._resolveExpectedDeviceConnectId(
|
|
1817
|
+
expectedIdentity.value,
|
|
1818
|
+
signal,
|
|
1819
|
+
operationContext
|
|
1820
|
+
);
|
|
1821
|
+
resolvedConnectId = resolved.connectId;
|
|
1822
|
+
selectionRequestId = resolved.selectionRequestId;
|
|
1823
|
+
}
|
|
1824
|
+
if (expectedIdentity?.value) {
|
|
1825
|
+
if (!operationId) await this._ensureSession(resolvedConnectId, signal);
|
|
1826
|
+
if (this._devices.get(resolvedConnectId)?.deviceId !== expectedIdentity.value) {
|
|
1827
|
+
return failure(
|
|
1828
|
+
HardwareErrorCode.DeviceMismatch,
|
|
1829
|
+
"Trezor device identity does not match"
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
const restorePassphraseRequestContext = this._setPassphraseRequestContext(
|
|
1834
|
+
resolvedConnectId,
|
|
1835
|
+
{
|
|
1836
|
+
passphraseState
|
|
1837
|
+
}
|
|
1838
|
+
);
|
|
1839
|
+
try {
|
|
1840
|
+
const result = await this._resolvePassphraseState(
|
|
1841
|
+
resolvedConnectId,
|
|
1842
|
+
passphraseState,
|
|
1843
|
+
signal,
|
|
1844
|
+
operationId
|
|
1845
|
+
);
|
|
1846
|
+
if (result.success && expectedIdentity?.value) {
|
|
1847
|
+
await this._emitVerifiedBinding(
|
|
1848
|
+
expectedIdentity.value,
|
|
1849
|
+
resolvedConnectId,
|
|
1850
|
+
selectionRequestId,
|
|
1851
|
+
operationContext,
|
|
1852
|
+
signal
|
|
1853
|
+
);
|
|
1854
|
+
verified = true;
|
|
1855
|
+
this._rememberVerifiedConnection(expectedIdentity.value, resolvedConnectId);
|
|
1856
|
+
}
|
|
1857
|
+
return result;
|
|
1858
|
+
} finally {
|
|
1859
|
+
restorePassphraseRequestContext();
|
|
1860
|
+
}
|
|
1403
1861
|
} finally {
|
|
1404
|
-
|
|
1862
|
+
if (selectionRequestId && !verified) {
|
|
1863
|
+
this._emitBindingStatus(selectionRequestId, signal.aborted ? "cancelled" : "failed");
|
|
1864
|
+
await this._releaseProvisionalConnection(resolvedConnectId, signal);
|
|
1865
|
+
}
|
|
1405
1866
|
}
|
|
1406
1867
|
},
|
|
1407
1868
|
{
|
|
@@ -1409,7 +1870,7 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1409
1870
|
rejectIfBusy: true,
|
|
1410
1871
|
busyError: _TrezorAdapter._createDeviceBusyError("getPassphraseState")
|
|
1411
1872
|
}
|
|
1412
|
-
).catch((error) => this._errorToFailure(error));
|
|
1873
|
+
).catch((error) => this._errorToFailure(error)).finally(() => releaseOperationRetention?.());
|
|
1413
1874
|
}
|
|
1414
1875
|
evmGetAddress(connectId, deviceId, params) {
|
|
1415
1876
|
return this._callWalletMethod("evmGetAddress", connectId, deviceId, params);
|
|
@@ -1472,11 +1933,100 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1472
1933
|
}
|
|
1473
1934
|
return result;
|
|
1474
1935
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1936
|
+
async _callConnector(sessionId, method, params, signal) {
|
|
1937
|
+
if (signal?.aborted) {
|
|
1938
|
+
throw signal.reason ?? new Error("Aborted");
|
|
1939
|
+
}
|
|
1940
|
+
this._assertConnectorIdle(method);
|
|
1941
|
+
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
1942
|
+
let promise;
|
|
1943
|
+
try {
|
|
1944
|
+
promise = this._connector.call(sessionId, method, params).finally(() => {
|
|
1945
|
+
releaseOperation();
|
|
1946
|
+
});
|
|
1947
|
+
} catch (error) {
|
|
1948
|
+
releaseOperation();
|
|
1949
|
+
throw error;
|
|
1950
|
+
}
|
|
1477
1951
|
const result = signal ? await _TrezorAdapter._abortable(signal, promise) : await promise;
|
|
1478
1952
|
return _TrezorAdapter._unwrapConnectorResult(result);
|
|
1479
1953
|
}
|
|
1954
|
+
_assertConnectorIdle(method) {
|
|
1955
|
+
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
1956
|
+
throw _TrezorAdapter._createDeviceBusyError(method);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
_runConnectorTeardown(task) {
|
|
1960
|
+
const previous = this._connectorTeardownTail;
|
|
1961
|
+
let releaseTail = () => void 0;
|
|
1962
|
+
this._connectorTeardownTail = new Promise((resolve) => {
|
|
1963
|
+
releaseTail = resolve;
|
|
1964
|
+
});
|
|
1965
|
+
this._pendingConnectorTeardowns += 1;
|
|
1966
|
+
return (async () => {
|
|
1967
|
+
try {
|
|
1968
|
+
await previous;
|
|
1969
|
+
await this._waitForConnectorOperationsToDrain();
|
|
1970
|
+
await task();
|
|
1971
|
+
} finally {
|
|
1972
|
+
this._pendingConnectorTeardowns -= 1;
|
|
1973
|
+
releaseTail();
|
|
1974
|
+
}
|
|
1975
|
+
})();
|
|
1976
|
+
}
|
|
1977
|
+
async _disconnectSession(connectId, sessionId = this._sessions.get(connectId)) {
|
|
1978
|
+
this._sessions.delete(connectId);
|
|
1979
|
+
this._verifiedPassphraseSessionsByConnectId.delete(connectId);
|
|
1980
|
+
if (sessionId) await this._runConnectorTeardown(() => this._connector.disconnect(sessionId));
|
|
1981
|
+
}
|
|
1982
|
+
_availableTransports() {
|
|
1983
|
+
return this._connector.availableTransports ?? [this._connector.connectionType];
|
|
1984
|
+
}
|
|
1985
|
+
/** Transports the connector offers and the host's model table allows; discovery opens no other. */
|
|
1986
|
+
_allowedTransports(context) {
|
|
1987
|
+
const supported = context?.supportedTransports;
|
|
1988
|
+
const available = this._availableTransports();
|
|
1989
|
+
return supported ? available.filter((transport) => supported.includes(transport)) : available;
|
|
1990
|
+
}
|
|
1991
|
+
async _releaseProvisionalConnection(connectId, signal) {
|
|
1992
|
+
const sessionId = this._sessions.get(connectId);
|
|
1993
|
+
this._sessions.delete(connectId);
|
|
1994
|
+
this._verifiedPassphraseSessionsByConnectId.delete(connectId);
|
|
1995
|
+
if (!sessionId) return;
|
|
1996
|
+
const teardown = this._runConnectorTeardown(() => this._connector.disconnect(sessionId)).catch(
|
|
1997
|
+
() => void 0
|
|
1998
|
+
);
|
|
1999
|
+
if (!signal.aborted) await teardown;
|
|
2000
|
+
}
|
|
2001
|
+
_waitForConnectorOperationsToDrain() {
|
|
2002
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2003
|
+
return Promise.resolve();
|
|
2004
|
+
}
|
|
2005
|
+
return new Promise((resolve) => {
|
|
2006
|
+
this._connectorIdleWaiters.add(resolve);
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
_retainConnectorOperation(key) {
|
|
2010
|
+
this._unsettledConnectorOperations.set(
|
|
2011
|
+
key,
|
|
2012
|
+
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2013
|
+
);
|
|
2014
|
+
let released = false;
|
|
2015
|
+
return () => {
|
|
2016
|
+
if (released) return;
|
|
2017
|
+
released = true;
|
|
2018
|
+
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2019
|
+
if (remaining > 0) {
|
|
2020
|
+
this._unsettledConnectorOperations.set(key, remaining);
|
|
2021
|
+
} else {
|
|
2022
|
+
this._unsettledConnectorOperations.delete(key);
|
|
2023
|
+
}
|
|
2024
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2025
|
+
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2026
|
+
this._connectorIdleWaiters.clear();
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
1480
2030
|
static _normalizeAllNetworkItem(method, item) {
|
|
1481
2031
|
if (method !== "btcGetAddress" && method !== "btcGetPublicKey") {
|
|
1482
2032
|
return item;
|
|
@@ -1495,18 +2045,14 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1495
2045
|
const { deviceId } = item;
|
|
1496
2046
|
return typeof deviceId === "string" && deviceId.length > 0 ? deviceId : void 0;
|
|
1497
2047
|
}
|
|
1498
|
-
_callAllNetworkMethod(connectId, method, item) {
|
|
2048
|
+
_callAllNetworkMethod(connectId, method, item, bundleContext) {
|
|
1499
2049
|
switch (method) {
|
|
1500
2050
|
case "evmGetAddress":
|
|
1501
|
-
return this.evmGetAddress(connectId, "", item);
|
|
1502
2051
|
case "btcGetAddress":
|
|
1503
|
-
return this.btcGetAddress(connectId, "", item);
|
|
1504
2052
|
case "btcGetPublicKey":
|
|
1505
|
-
return this.btcGetPublicKey(connectId, "", item);
|
|
1506
2053
|
case "solGetAddress":
|
|
1507
|
-
return this.solGetAddress(connectId, "", item);
|
|
1508
2054
|
case "tronGetAddress":
|
|
1509
|
-
return this.
|
|
2055
|
+
return this._callMethod(method, connectId, "", item, true, bundleContext);
|
|
1510
2056
|
default:
|
|
1511
2057
|
return Promise.resolve(
|
|
1512
2058
|
failure(
|
|
@@ -1525,11 +2071,51 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1525
2071
|
_callWalletMethod(methodName, connectId, deviceId, params) {
|
|
1526
2072
|
return this._callMethod(methodName, connectId, deviceId, params, true);
|
|
1527
2073
|
}
|
|
1528
|
-
_callDeviceManagerMethod(methodName, connectId, params) {
|
|
1529
|
-
|
|
2074
|
+
_callDeviceManagerMethod(methodName, connectId, params, operationContext) {
|
|
2075
|
+
const expectedIdentity = operationContext?.expectedDeviceIdentity;
|
|
2076
|
+
if (expectedIdentity && (expectedIdentity.vendor !== "trezor" || expectedIdentity.type !== "deviceId")) {
|
|
2077
|
+
return Promise.resolve(
|
|
2078
|
+
failure(
|
|
2079
|
+
HardwareErrorCode.InvalidParams,
|
|
2080
|
+
"Trezor device-manager operations require a Trezor deviceId identity"
|
|
2081
|
+
)
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
return this._callMethod(
|
|
2085
|
+
methodName,
|
|
2086
|
+
connectId,
|
|
2087
|
+
expectedIdentity?.value,
|
|
2088
|
+
{
|
|
2089
|
+
...params && typeof params === "object" ? params : {},
|
|
2090
|
+
knownConnections: operationContext?.knownConnections,
|
|
2091
|
+
extra: operationContext?.extra,
|
|
2092
|
+
allowDeviceSelection: operationContext?.allowDeviceSelection,
|
|
2093
|
+
supportedTransports: operationContext?.supportedTransports,
|
|
2094
|
+
...operationContext?.operationId ? { operationId: operationContext.operationId } : {}
|
|
2095
|
+
},
|
|
2096
|
+
false
|
|
2097
|
+
);
|
|
1530
2098
|
}
|
|
1531
|
-
async _callMethod(methodName, connectId, deviceId, params, requiresWalletIntent) {
|
|
2099
|
+
async _callMethod(methodName, connectId, deviceId, params, requiresWalletIntent, bundleContext) {
|
|
1532
2100
|
const call = _TrezorAdapter._normalizeCallArgs(connectId, deviceId, params);
|
|
2101
|
+
const commonParams = _TrezorAdapter._splitCommonParams(call.params);
|
|
2102
|
+
const positionalOperationId = isHardwareOperationId(call.connectId) ? call.connectId : void 0;
|
|
2103
|
+
if (positionalOperationId && commonParams.operationId && positionalOperationId !== commonParams.operationId) {
|
|
2104
|
+
return failure(HardwareErrorCode.InvalidParams, "Conflicting Trezor operation ids", {
|
|
2105
|
+
positionalOperationId,
|
|
2106
|
+
commonOperationId: commonParams.operationId
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
const operationId = commonParams.operationId ?? positionalOperationId;
|
|
2110
|
+
let releaseOperationRetention;
|
|
2111
|
+
try {
|
|
2112
|
+
if (operationId) {
|
|
2113
|
+
call.connectId = this._operations.resolve(operationId).connectId;
|
|
2114
|
+
releaseOperationRetention = this._operations.retain(operationId);
|
|
2115
|
+
}
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
return this._errorToFailure(error);
|
|
2118
|
+
}
|
|
1533
2119
|
debugLog("[TrezorAdapter][REQ]", {
|
|
1534
2120
|
method: methodName,
|
|
1535
2121
|
connectId: call.connectId || "(empty)",
|
|
@@ -1538,15 +2124,40 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1538
2124
|
});
|
|
1539
2125
|
try {
|
|
1540
2126
|
const response = await this._jobQueue.enqueue(
|
|
1541
|
-
call.connectId,
|
|
1542
|
-
(signal) =>
|
|
1543
|
-
call.connectId
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
2127
|
+
trezorQueueKey({ operationId, connectId: call.connectId }),
|
|
2128
|
+
async (signal) => {
|
|
2129
|
+
let effectiveConnectId = call.connectId;
|
|
2130
|
+
let selectionRequestId;
|
|
2131
|
+
try {
|
|
2132
|
+
if (!bundleContext?.connection && !operationId && deviceId && (!connectId || commonParams.connectionContext?.knownConnections !== void 0)) {
|
|
2133
|
+
const resolved = await this._resolveExpectedDeviceConnectId(
|
|
2134
|
+
deviceId,
|
|
2135
|
+
signal,
|
|
2136
|
+
commonParams.connectionContext
|
|
2137
|
+
);
|
|
2138
|
+
effectiveConnectId = resolved.connectId;
|
|
2139
|
+
selectionRequestId = resolved.selectionRequestId;
|
|
2140
|
+
}
|
|
2141
|
+
} catch (error) {
|
|
2142
|
+
return this._errorToFailure(error);
|
|
2143
|
+
}
|
|
2144
|
+
const response2 = await this._callWithRetry(
|
|
2145
|
+
bundleContext?.connection?.connectId ?? effectiveConnectId,
|
|
2146
|
+
methodName,
|
|
2147
|
+
call.params,
|
|
2148
|
+
deviceId || "",
|
|
2149
|
+
requiresWalletIntent,
|
|
2150
|
+
true,
|
|
2151
|
+
signal,
|
|
2152
|
+
operationId,
|
|
2153
|
+
selectionRequestId,
|
|
2154
|
+
bundleContext
|
|
2155
|
+
);
|
|
2156
|
+
if (!response2.success && selectionRequestId) {
|
|
2157
|
+
await this._releaseProvisionalConnection(effectiveConnectId, signal);
|
|
2158
|
+
}
|
|
2159
|
+
return response2;
|
|
2160
|
+
},
|
|
1550
2161
|
{
|
|
1551
2162
|
label: methodName,
|
|
1552
2163
|
rejectIfBusy: true,
|
|
@@ -1556,7 +2167,7 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1556
2167
|
debugLog("[TrezorAdapter][RES]", {
|
|
1557
2168
|
method: methodName,
|
|
1558
2169
|
success: response.success,
|
|
1559
|
-
payload: _TrezorAdapter.
|
|
2170
|
+
payload: _TrezorAdapter._responseForLog(response, methodName)
|
|
1560
2171
|
});
|
|
1561
2172
|
return response;
|
|
1562
2173
|
} catch (error) {
|
|
@@ -1564,19 +2175,56 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1564
2175
|
debugLog("[TrezorAdapter][RES]", {
|
|
1565
2176
|
method: methodName,
|
|
1566
2177
|
success: false,
|
|
1567
|
-
payload: _TrezorAdapter.
|
|
2178
|
+
payload: _TrezorAdapter._responseForLog(response, methodName)
|
|
1568
2179
|
});
|
|
1569
2180
|
return response;
|
|
2181
|
+
} finally {
|
|
2182
|
+
releaseOperationRetention?.();
|
|
1570
2183
|
}
|
|
1571
2184
|
}
|
|
1572
|
-
async _callWithRetry(connectId, methodName, params, requiresWalletIntent, allowRetry, signal) {
|
|
1573
|
-
const { passphraseState, useEmptyPassphrase, rest } = _TrezorAdapter._splitCommonParams(params);
|
|
2185
|
+
async _callWithRetry(connectId, methodName, params, expectedDeviceId, requiresWalletIntent, allowRetry, signal, operationId, selectionRequestId, bundleContext) {
|
|
2186
|
+
const { passphraseState, useEmptyPassphrase, rest, connectionContext } = _TrezorAdapter._splitCommonParams(params);
|
|
2187
|
+
let pendingBindingRequestId = selectionRequestId;
|
|
2188
|
+
let businessCallStarted = false;
|
|
1574
2189
|
const restorePassphraseRequestContext = this._setPassphraseRequestContext(connectId, {
|
|
1575
2190
|
passphraseState,
|
|
1576
2191
|
useEmptyPassphrase
|
|
1577
2192
|
});
|
|
1578
2193
|
try {
|
|
1579
|
-
|
|
2194
|
+
if (bundleContext?.connection) {
|
|
2195
|
+
const pinned = bundleContext.connection;
|
|
2196
|
+
if (this._sessions.get(pinned.connectId) !== pinned.sessionId) {
|
|
2197
|
+
return failure(
|
|
2198
|
+
HardwareErrorCode.DeviceDisconnected,
|
|
2199
|
+
"Trezor all-network connection ended"
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
const sessionId = operationId ? this._sessions.get(this._operations.resolve(operationId).connectId) : await this._ensureSession(connectId, signal);
|
|
2204
|
+
if (!sessionId) {
|
|
2205
|
+
if (operationId) {
|
|
2206
|
+
this._operations.end(operationId, "disconnect");
|
|
2207
|
+
return failure(
|
|
2208
|
+
HardwareErrorCode.OperationEnded,
|
|
2209
|
+
"Trezor operation connection is no longer active",
|
|
2210
|
+
{ operationId, reason: "disconnect" }
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
return failure(HardwareErrorCode.DeviceNotFound, "Trezor session was not found");
|
|
2214
|
+
}
|
|
2215
|
+
if (bundleContext && !bundleContext.connection) {
|
|
2216
|
+
bundleContext.connection = { connectId, sessionId };
|
|
2217
|
+
}
|
|
2218
|
+
if (expectedDeviceId) {
|
|
2219
|
+
const actualDeviceId = this._devices.get(connectId)?.deviceId;
|
|
2220
|
+
if (!actualDeviceId || actualDeviceId !== expectedDeviceId) {
|
|
2221
|
+
return failure(
|
|
2222
|
+
HardwareErrorCode.DeviceMismatch,
|
|
2223
|
+
`Wrong device: expected ${expectedDeviceId}, got ${actualDeviceId || "unknown"}`,
|
|
2224
|
+
{ expected: expectedDeviceId, actual: actualDeviceId || "" }
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
1580
2228
|
if (requiresWalletIntent && !passphraseState && useEmptyPassphrase !== true) {
|
|
1581
2229
|
return failure(
|
|
1582
2230
|
HardwareErrorCode.InvalidParams,
|
|
@@ -1591,39 +2239,85 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1591
2239
|
} else if (useEmptyPassphrase === true) {
|
|
1592
2240
|
await _TrezorAdapter._abortable(signal, this._createFreshAppSession(sessionId, signal));
|
|
1593
2241
|
}
|
|
2242
|
+
if (expectedDeviceId) {
|
|
2243
|
+
await this._emitVerifiedBinding(
|
|
2244
|
+
expectedDeviceId,
|
|
2245
|
+
connectId,
|
|
2246
|
+
pendingBindingRequestId,
|
|
2247
|
+
connectionContext,
|
|
2248
|
+
signal
|
|
2249
|
+
);
|
|
2250
|
+
pendingBindingRequestId = void 0;
|
|
2251
|
+
this._rememberVerifiedConnection(expectedDeviceId, connectId);
|
|
2252
|
+
}
|
|
2253
|
+
businessCallStarted = true;
|
|
1594
2254
|
const result = await _TrezorAdapter._abortable(
|
|
1595
2255
|
signal,
|
|
1596
|
-
|
|
2256
|
+
this._callConnector(sessionId, methodName, rest, signal)
|
|
1597
2257
|
);
|
|
1598
2258
|
return success(result);
|
|
1599
2259
|
} catch (error) {
|
|
1600
2260
|
if (signal.aborted) {
|
|
2261
|
+
if (pendingBindingRequestId) {
|
|
2262
|
+
this._emitBindingStatus(pendingBindingRequestId, "cancelled");
|
|
2263
|
+
pendingBindingRequestId = void 0;
|
|
2264
|
+
}
|
|
1601
2265
|
return this._errorToFailure(error);
|
|
1602
2266
|
}
|
|
1603
2267
|
const code = _TrezorAdapter._errorCode(error);
|
|
1604
|
-
|
|
2268
|
+
const willRetryStaleSession = allowRetry && (passphraseState || useEmptyPassphrase === true) && _TrezorAdapter._isStaleSessionError(code);
|
|
2269
|
+
if (pendingBindingRequestId && !willRetryStaleSession) {
|
|
2270
|
+
this._emitBindingStatus(pendingBindingRequestId, "failed");
|
|
2271
|
+
pendingBindingRequestId = void 0;
|
|
2272
|
+
}
|
|
2273
|
+
const ambiguousTransportFailure = code === HardwareErrorCode.DeviceDisconnected || code === HardwareErrorCode.OperationTimeout || code === HardwareErrorCode.TransportError;
|
|
2274
|
+
if (ambiguousTransportFailure) {
|
|
1605
2275
|
this._sessions.delete(connectId);
|
|
1606
2276
|
this._verifiedPassphraseSessionsByConnectId.delete(connectId);
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
2277
|
+
const mayHaveCompleted = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(methodName);
|
|
2278
|
+
const mayHaveCompletedMessage = `Trezor ${methodName} may have completed before the connection was lost`;
|
|
2279
|
+
if (operationId) {
|
|
2280
|
+
this._operations.end(operationId, "disconnect");
|
|
2281
|
+
const endedParams = { operationId, reason: "disconnect" };
|
|
2282
|
+
return mayHaveCompleted ? failure(
|
|
2283
|
+
HardwareErrorCode.OperationEnded,
|
|
2284
|
+
mayHaveCompletedMessage,
|
|
2285
|
+
operationMayHaveCompletedParams(methodName, endedParams),
|
|
2286
|
+
void 0,
|
|
2287
|
+
{ scope: "unknown" }
|
|
2288
|
+
) : failure(
|
|
2289
|
+
HardwareErrorCode.OperationEnded,
|
|
2290
|
+
"Trezor operation connection was lost",
|
|
2291
|
+
endedParams
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
if (mayHaveCompleted) {
|
|
2295
|
+
return failure(
|
|
2296
|
+
code,
|
|
2297
|
+
mayHaveCompletedMessage,
|
|
2298
|
+
operationMayHaveCompletedParams(methodName),
|
|
2299
|
+
void 0,
|
|
2300
|
+
{ scope: "unknown" }
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
1615
2303
|
}
|
|
1616
|
-
if (
|
|
2304
|
+
if (willRetryStaleSession) {
|
|
1617
2305
|
if (passphraseState) {
|
|
1618
2306
|
this._forgetVerifiedPassphraseSession(connectId, passphraseState);
|
|
1619
2307
|
}
|
|
2308
|
+
const retryBindingRequestId = pendingBindingRequestId;
|
|
2309
|
+
pendingBindingRequestId = void 0;
|
|
1620
2310
|
return await this._callWithRetry(
|
|
1621
2311
|
connectId,
|
|
1622
2312
|
methodName,
|
|
1623
2313
|
params,
|
|
2314
|
+
expectedDeviceId,
|
|
1624
2315
|
requiresWalletIntent,
|
|
1625
2316
|
false,
|
|
1626
|
-
signal
|
|
2317
|
+
signal,
|
|
2318
|
+
operationId,
|
|
2319
|
+
retryBindingRequestId,
|
|
2320
|
+
bundleContext
|
|
1627
2321
|
);
|
|
1628
2322
|
}
|
|
1629
2323
|
debugLog("[TrezorAdapter][ERROR]", {
|
|
@@ -1634,36 +2328,249 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1634
2328
|
return this._errorToFailure(error);
|
|
1635
2329
|
} finally {
|
|
1636
2330
|
restorePassphraseRequestContext();
|
|
2331
|
+
if (pendingBindingRequestId) {
|
|
2332
|
+
this._emitBindingStatus(pendingBindingRequestId, signal.aborted ? "cancelled" : "failed");
|
|
2333
|
+
pendingBindingRequestId = void 0;
|
|
2334
|
+
}
|
|
1637
2335
|
}
|
|
1638
2336
|
}
|
|
1639
|
-
async _ensureSession(connectId, signal) {
|
|
2337
|
+
async _ensureSession(connectId, signal, transportType) {
|
|
1640
2338
|
const existing = this._sessions.get(connectId);
|
|
1641
2339
|
if (existing) return existing;
|
|
1642
2340
|
const pending = this._connectingPromises.get(connectId);
|
|
1643
2341
|
if (pending) return pending;
|
|
2342
|
+
const stateGeneration = this._stateGeneration;
|
|
1644
2343
|
const promise = (async () => {
|
|
2344
|
+
if (signal?.aborted) {
|
|
2345
|
+
throw Object.assign(new Error("Trezor connection aborted"), {
|
|
2346
|
+
code: HardwareErrorCode.UserAborted
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
1645
2349
|
try {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
try {
|
|
1652
|
-
return await this._connectSession(connectId, signal);
|
|
1653
|
-
} catch (error) {
|
|
1654
|
-
if (_TrezorAdapter._isConnectionUnavailableError(error)) {
|
|
1655
|
-
this._sessions.delete(connectId);
|
|
1656
|
-
this._verifiedPassphraseSessionsByConnectId.delete(connectId);
|
|
1657
|
-
}
|
|
1658
|
-
throw error;
|
|
2350
|
+
return await this._connectSession(connectId, signal, stateGeneration, transportType);
|
|
2351
|
+
} catch (error) {
|
|
2352
|
+
if (_TrezorAdapter._isConnectionUnavailableError(error)) {
|
|
2353
|
+
this._sessions.delete(connectId);
|
|
2354
|
+
this._verifiedPassphraseSessionsByConnectId.delete(connectId);
|
|
1659
2355
|
}
|
|
1660
|
-
|
|
1661
|
-
this._connectingPromises.delete(connectId);
|
|
1662
|
-
this._disconnectRequested.delete(connectId);
|
|
2356
|
+
throw error;
|
|
1663
2357
|
}
|
|
1664
2358
|
})();
|
|
1665
2359
|
this._connectingPromises.set(connectId, promise);
|
|
1666
|
-
|
|
2360
|
+
try {
|
|
2361
|
+
return await promise;
|
|
2362
|
+
} finally {
|
|
2363
|
+
if (this._connectingPromises.get(connectId) === promise) {
|
|
2364
|
+
this._connectingPromises.delete(connectId);
|
|
2365
|
+
}
|
|
2366
|
+
this._disconnectRequested.delete(connectId);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
/** An identity probe tears down only a session it opened; another caller's stays up. */
|
|
2370
|
+
_hasSessionOrConnect(connectId) {
|
|
2371
|
+
return this._sessions.has(connectId) || this._connectingPromises.has(connectId);
|
|
2372
|
+
}
|
|
2373
|
+
async _resolveExpectedDeviceConnectId(expectedDeviceId, signal, context) {
|
|
2374
|
+
const connectedMatch = Array.from(this._devices.values()).find(
|
|
2375
|
+
(device) => device.deviceId === expectedDeviceId && this._sessions.has(device.connectId)
|
|
2376
|
+
);
|
|
2377
|
+
if (connectedMatch) return { connectId: connectedMatch.connectId };
|
|
2378
|
+
const allowedTransports = this._allowedTransports(context);
|
|
2379
|
+
if (allowedTransports.includes("usb")) {
|
|
2380
|
+
await _TrezorAdapter._abortable(
|
|
2381
|
+
signal,
|
|
2382
|
+
this._ensureDevicePermission(void 0, expectedDeviceId, "usb")
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
const candidates = allowedTransports.includes("usb") ? await _TrezorAdapter._abortable(
|
|
2386
|
+
signal,
|
|
2387
|
+
this._searchDevices({ transportType: "usb" }, signal)
|
|
2388
|
+
) : [];
|
|
2389
|
+
const mismatchedDeviceIds = [];
|
|
2390
|
+
const knownConnection = this._knownDeviceConnections.get(expectedDeviceId);
|
|
2391
|
+
const knownUsbIds = context?.knownConnections !== void 0 ? context.knownConnections.flatMap(
|
|
2392
|
+
(connection) => connection.transport === "usb" ? [connection.connectId] : []
|
|
2393
|
+
) : [knownConnection?.usbConnectId].filter((id) => Boolean(id));
|
|
2394
|
+
const suppliedBleIds = context?.knownConnections?.flatMap(
|
|
2395
|
+
(connection) => connection.transport === "ble" ? [connection.connectId] : []
|
|
2396
|
+
);
|
|
2397
|
+
const knownBleIds = suppliedBleIds?.length ? suppliedBleIds : [knownConnection?.bleConnectId].filter((id) => Boolean(id));
|
|
2398
|
+
const usbCandidates = candidates.filter(
|
|
2399
|
+
(device) => device.connectionType === "usb" && (!knownUsbIds.length || knownUsbIds.includes(device.connectId))
|
|
2400
|
+
);
|
|
2401
|
+
for (const candidate of usbCandidates) {
|
|
2402
|
+
let sessionId;
|
|
2403
|
+
const probeOpensSession = !this._hasSessionOrConnect(candidate.connectId);
|
|
2404
|
+
try {
|
|
2405
|
+
sessionId = await this._ensureSession(candidate.connectId, signal, "usb");
|
|
2406
|
+
const actualDeviceId = this._devices.get(candidate.connectId)?.deviceId ?? "";
|
|
2407
|
+
if (actualDeviceId === expectedDeviceId) return { connectId: candidate.connectId };
|
|
2408
|
+
mismatchedDeviceIds.push(actualDeviceId || "unknown");
|
|
2409
|
+
} catch (error) {
|
|
2410
|
+
if (signal.aborted) throw error;
|
|
2411
|
+
if (!_TrezorAdapter._isConnectionUnavailableError(error)) throw error;
|
|
2412
|
+
}
|
|
2413
|
+
if (sessionId && probeOpensSession) {
|
|
2414
|
+
await this._disconnectSession(candidate.connectId, sessionId);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
if (mismatchedDeviceIds.length) {
|
|
2418
|
+
throw createHwkError({
|
|
2419
|
+
code: HardwareErrorCode.DeviceSearchMismatch,
|
|
2420
|
+
message: "The connected Trezor is not the wallet this operation needs. Unplug it and connect the right device.",
|
|
2421
|
+
params: { connectedDeviceIds: mismatchedDeviceIds }
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
if (usbCandidates.length > 0) {
|
|
2425
|
+
throw createHwkError({
|
|
2426
|
+
code: HardwareErrorCode.DeviceNotFound,
|
|
2427
|
+
message: "Trezor devices are present but none could be opened to check its identity."
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
const [knownBleConnectId] = knownBleIds;
|
|
2431
|
+
if (knownBleConnectId && allowedTransports.includes("ble")) {
|
|
2432
|
+
await _TrezorAdapter._abortable(
|
|
2433
|
+
signal,
|
|
2434
|
+
this._ensureDevicePermission(knownBleConnectId, expectedDeviceId, "ble")
|
|
2435
|
+
);
|
|
2436
|
+
const probeOpensSession = !this._hasSessionOrConnect(knownBleConnectId);
|
|
2437
|
+
await this._ensureSession(knownBleConnectId, signal, "ble");
|
|
2438
|
+
const actualDeviceId = this._devices.get(knownBleConnectId)?.deviceId;
|
|
2439
|
+
if (actualDeviceId === expectedDeviceId) return { connectId: knownBleConnectId };
|
|
2440
|
+
if (probeOpensSession) await this._disconnectSession(knownBleConnectId);
|
|
2441
|
+
throw createHwkError({
|
|
2442
|
+
code: HardwareErrorCode.DeviceMismatch,
|
|
2443
|
+
message: "The bound Trezor Bluetooth device has a different identity"
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
|
|
2447
|
+
throw createHwkError({
|
|
2448
|
+
code: HardwareErrorCode.DeviceNotFound,
|
|
2449
|
+
message: "This Trezor model has no Bluetooth transport; connect it over USB"
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
if (context?.allowDeviceSelection === false) {
|
|
2453
|
+
throw createHwkError({
|
|
2454
|
+
code: HardwareErrorCode.DeviceNotFound,
|
|
2455
|
+
message: "No known Trezor connection is available"
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
return this._selectBleDeviceForBinding(expectedDeviceId, signal, context, "missing-binding");
|
|
2459
|
+
}
|
|
2460
|
+
async _selectBleDeviceForBinding(expectedDeviceId, signal, context, reason) {
|
|
2461
|
+
const availableTransports = this._allowedTransports(context);
|
|
2462
|
+
const allowUsbFallback = reason !== "manual-rebind" && availableTransports.includes("usb");
|
|
2463
|
+
const knownUsbIds = context?.knownConnections ? context.knownConnections.flatMap(
|
|
2464
|
+
(connection) => connection.transport === "usb" ? [connection.connectId] : []
|
|
2465
|
+
) : [this._knownDeviceConnections.get(expectedDeviceId)?.usbConnectId];
|
|
2466
|
+
if (availableTransports.includes("ble")) {
|
|
2467
|
+
await _TrezorAdapter._abortable(
|
|
2468
|
+
signal,
|
|
2469
|
+
this._ensureDevicePermission(void 0, expectedDeviceId, "ble")
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
const bleCandidates = (await _TrezorAdapter._abortable(
|
|
2473
|
+
signal,
|
|
2474
|
+
this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)
|
|
2475
|
+
)).filter((device) => device.connectionType === "ble");
|
|
2476
|
+
if (signal.aborted) throw signal.reason;
|
|
2477
|
+
const rejectedConnectIds = /* @__PURE__ */ new Set();
|
|
2478
|
+
const bindingSessionId = this._uiRegistry.createRequestId();
|
|
2479
|
+
let rejectedConnectId;
|
|
2480
|
+
while (availableTransports.includes("ble") && this._emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2481
|
+
const { device: selected, requestId } = await requestBleDeviceSelection({
|
|
2482
|
+
emitter: this._emitter,
|
|
2483
|
+
registry: this._uiRegistry,
|
|
2484
|
+
signal,
|
|
2485
|
+
allowUsbFallback,
|
|
2486
|
+
scan: async () => {
|
|
2487
|
+
if (allowUsbFallback) {
|
|
2488
|
+
const usbCandidates = (await this._searchDevices({ transportType: "usb" }, signal)).filter(
|
|
2489
|
+
(device) => device.connectionType === "usb" && !rejectedConnectIds.has(device.connectId)
|
|
2490
|
+
);
|
|
2491
|
+
const candidate = usbCandidates.find((device) => knownUsbIds.includes(device.connectId)) ?? usbCandidates[0];
|
|
2492
|
+
if (candidate) return [candidate];
|
|
2493
|
+
}
|
|
2494
|
+
return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter(
|
|
2495
|
+
(device) => device.connectionType === "ble" && !rejectedConnectIds.has(device.connectId)
|
|
2496
|
+
);
|
|
2497
|
+
},
|
|
2498
|
+
request: {
|
|
2499
|
+
devices: bleCandidates.filter((device) => !rejectedConnectIds.has(device.connectId)),
|
|
2500
|
+
bindingSessionId,
|
|
2501
|
+
rejectedConnectId,
|
|
2502
|
+
context: {
|
|
2503
|
+
kind: "bind-connection",
|
|
2504
|
+
transport: "ble",
|
|
2505
|
+
reason
|
|
2506
|
+
},
|
|
2507
|
+
extra: context?.extra,
|
|
2508
|
+
operationId: this._activeOperationId()
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
let probeOpensSession = false;
|
|
2512
|
+
try {
|
|
2513
|
+
if (reason === "manual-rebind") {
|
|
2514
|
+
this._operations.endByConnectionKey(selected.connectId, "explicit");
|
|
2515
|
+
await this._releaseProvisionalConnection(selected.connectId, signal);
|
|
2516
|
+
}
|
|
2517
|
+
probeOpensSession = !this._hasSessionOrConnect(selected.connectId);
|
|
2518
|
+
await this._ensureSession(selected.connectId, signal, selected.connectionType);
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
this._emitBindingStatus(requestId, signal.aborted ? "cancelled" : "failed");
|
|
2521
|
+
throw error;
|
|
2522
|
+
}
|
|
2523
|
+
if (this._devices.get(selected.connectId)?.deviceId !== expectedDeviceId) {
|
|
2524
|
+
if (probeOpensSession) await this._disconnectSession(selected.connectId);
|
|
2525
|
+
rejectedConnectId = selected.connectId;
|
|
2526
|
+
rejectedConnectIds.add(selected.connectId);
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
if (selected.connectionType === "usb") {
|
|
2530
|
+
this._emitBindingStatus(requestId, "cancelled");
|
|
2531
|
+
return { connectId: selected.connectId };
|
|
2532
|
+
}
|
|
2533
|
+
return { connectId: selected.connectId, selectionRequestId: requestId };
|
|
2534
|
+
}
|
|
2535
|
+
throw createHwkError({
|
|
2536
|
+
code: HardwareErrorCode.DeviceNotFound,
|
|
2537
|
+
message: "No readable Trezor device is available"
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
_emitBindingStatus(selectionRequestId, status) {
|
|
2541
|
+
emitDeviceBindingStatus(this._emitter, selectionRequestId, status);
|
|
2542
|
+
}
|
|
2543
|
+
_rememberVerifiedConnection(deviceId, connectId) {
|
|
2544
|
+
const device = this._devices.get(connectId);
|
|
2545
|
+
if (!device || device.deviceId !== deviceId) return;
|
|
2546
|
+
const known = this._knownDeviceConnections.get(deviceId) ?? { deviceId };
|
|
2547
|
+
if (device.connectionType === "ble") {
|
|
2548
|
+
known.bleConnectId = connectId;
|
|
2549
|
+
} else if (device.connectionType === "usb" && device.capabilities?.persistentDeviceIdentity) {
|
|
2550
|
+
known.usbConnectId = connectId;
|
|
2551
|
+
}
|
|
2552
|
+
this._knownDeviceConnections.set(deviceId, known);
|
|
2553
|
+
}
|
|
2554
|
+
async _emitVerifiedBinding(deviceId, connectId, selectionRequestId, context, signal) {
|
|
2555
|
+
if (!selectionRequestId) return false;
|
|
2556
|
+
const device = this._devices.get(connectId);
|
|
2557
|
+
if (!device || device.deviceId !== deviceId || device.connectionType !== "ble") {
|
|
2558
|
+
this._emitBindingStatus(selectionRequestId, "cancelled");
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
const outcome = await requestSaveDeviceBinding(
|
|
2562
|
+
this._emitter,
|
|
2563
|
+
this._uiRegistry,
|
|
2564
|
+
{
|
|
2565
|
+
selectionRequestId,
|
|
2566
|
+
connection: { transport: "ble", connectId },
|
|
2567
|
+
identity: { vendor: "trezor", type: "deviceId", value: deviceId },
|
|
2568
|
+
extra: context?.extra,
|
|
2569
|
+
operationId: this._activeOperationId()
|
|
2570
|
+
},
|
|
2571
|
+
signal
|
|
2572
|
+
);
|
|
2573
|
+
return outcome.saved;
|
|
1667
2574
|
}
|
|
1668
2575
|
/**
|
|
1669
2576
|
* Pin the device's active wallet session to `passphraseState` (the target
|
|
@@ -1685,12 +2592,21 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1685
2592
|
if (reused) return;
|
|
1686
2593
|
const created = await _TrezorAdapter._abortable(
|
|
1687
2594
|
signal,
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
2595
|
+
this._callConnector(
|
|
2596
|
+
sessionId,
|
|
2597
|
+
"__thpCreateSession",
|
|
2598
|
+
{
|
|
2599
|
+
passphraseMode: "prompt"
|
|
2600
|
+
},
|
|
2601
|
+
signal
|
|
2602
|
+
)
|
|
1691
2603
|
);
|
|
1692
|
-
if (created?.protocol === "thp" && !created.thpSessionId)
|
|
1693
|
-
|
|
2604
|
+
if (created?.protocol === "thp" && !created.thpSessionId || created?.protocol !== "thp" && created?.protocol !== "v1") {
|
|
2605
|
+
throw createHwkError({
|
|
2606
|
+
code: HardwareErrorCode.TransportError,
|
|
2607
|
+
message: "Cannot verify the requested Trezor wallet session"
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
1694
2610
|
const state = await this._deriveState(sessionId, signal);
|
|
1695
2611
|
if (state !== passphraseState) {
|
|
1696
2612
|
throw Object.assign(
|
|
@@ -1740,9 +2656,14 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1740
2656
|
if (!cached.thpSessionId) return false;
|
|
1741
2657
|
await _TrezorAdapter._abortable(
|
|
1742
2658
|
signal,
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
2659
|
+
this._callConnector(
|
|
2660
|
+
sessionId,
|
|
2661
|
+
"__thpSelectSession",
|
|
2662
|
+
{
|
|
2663
|
+
thpSessionId: cached.thpSessionId
|
|
2664
|
+
},
|
|
2665
|
+
signal
|
|
2666
|
+
)
|
|
1746
2667
|
);
|
|
1747
2668
|
}
|
|
1748
2669
|
const state = await this._deriveState(sessionId, signal);
|
|
@@ -1760,9 +2681,14 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1760
2681
|
async _createFreshAppSession(sessionId, signal) {
|
|
1761
2682
|
await _TrezorAdapter._abortable(
|
|
1762
2683
|
signal,
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2684
|
+
this._callConnector(
|
|
2685
|
+
sessionId,
|
|
2686
|
+
"__thpCreateSession",
|
|
2687
|
+
{
|
|
2688
|
+
passphraseMode: "empty"
|
|
2689
|
+
},
|
|
2690
|
+
signal
|
|
2691
|
+
)
|
|
1766
2692
|
);
|
|
1767
2693
|
}
|
|
1768
2694
|
/**
|
|
@@ -1777,10 +2703,15 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1777
2703
|
async _deriveState(sessionId, signal) {
|
|
1778
2704
|
const derived = await _TrezorAdapter._abortable(
|
|
1779
2705
|
signal,
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
2706
|
+
this._callConnector(
|
|
2707
|
+
sessionId,
|
|
2708
|
+
"btcGetPublicKey",
|
|
2709
|
+
{
|
|
2710
|
+
path: "m/44'/0'/0'",
|
|
2711
|
+
showOnDevice: false
|
|
2712
|
+
},
|
|
2713
|
+
signal
|
|
2714
|
+
)
|
|
1784
2715
|
);
|
|
1785
2716
|
const state = derived?.publicKey;
|
|
1786
2717
|
if (!state) {
|
|
@@ -1790,23 +2721,34 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1790
2721
|
}
|
|
1791
2722
|
return state;
|
|
1792
2723
|
}
|
|
1793
|
-
async _resolvePassphraseState(connectId, passphraseState, signal) {
|
|
2724
|
+
async _resolvePassphraseState(connectId, passphraseState, signal, operationId) {
|
|
1794
2725
|
try {
|
|
1795
|
-
const sessionId = await this._ensureSession(connectId, signal);
|
|
2726
|
+
const sessionId = operationId ? this._sessions.get(this._operations.resolve(operationId).connectId) : await this._ensureSession(connectId, signal);
|
|
2727
|
+
if (!sessionId) {
|
|
2728
|
+
return failure(
|
|
2729
|
+
HardwareErrorCode.OperationEnded,
|
|
2730
|
+
"Trezor operation connection is no longer active"
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
1796
2733
|
if (passphraseState) {
|
|
1797
2734
|
await this._alignAppSession(connectId, sessionId, passphraseState, signal);
|
|
1798
2735
|
return success(passphraseState);
|
|
1799
2736
|
}
|
|
1800
2737
|
const created = await _TrezorAdapter._abortable(
|
|
1801
2738
|
signal,
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2739
|
+
this._callConnector(
|
|
2740
|
+
sessionId,
|
|
2741
|
+
"__thpCreateSession",
|
|
2742
|
+
{
|
|
2743
|
+
passphraseMode: "prompt"
|
|
2744
|
+
},
|
|
2745
|
+
signal
|
|
2746
|
+
)
|
|
1805
2747
|
);
|
|
1806
2748
|
const state = await this._deriveState(sessionId, signal);
|
|
1807
2749
|
const features = await _TrezorAdapter._abortable(
|
|
1808
2750
|
signal,
|
|
1809
|
-
|
|
2751
|
+
this._callConnector(sessionId, "getFeatures", { refresh: true }, signal)
|
|
1810
2752
|
);
|
|
1811
2753
|
if (features?.passphrase_protection !== true) {
|
|
1812
2754
|
return success(null);
|
|
@@ -1856,7 +2798,16 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1856
2798
|
}
|
|
1857
2799
|
const trustedNumericCode = typeof typed.code === "number" && KNOWN_HARDWARE_ERROR_CODES.has(typed.code) ? typed.code : void 0;
|
|
1858
2800
|
const code = trustedNumericCode ?? _TrezorAdapter._mapTrezorFailureCode(error) ?? HardwareErrorCode.UnknownError;
|
|
1859
|
-
|
|
2801
|
+
const stampedOrigin = error?.origin;
|
|
2802
|
+
const origin = stampedOrigin === "device" || stampedOrigin === "transport" || stampedOrigin === "host" ? stampedOrigin : defaultOriginForCode(code);
|
|
2803
|
+
const params = typed.params && typeof typed.params === "object" ? typed.params : void 0;
|
|
2804
|
+
return failure(
|
|
2805
|
+
code,
|
|
2806
|
+
typed.message ?? String(error),
|
|
2807
|
+
params,
|
|
2808
|
+
origin,
|
|
2809
|
+
isHwkRecoveryHint(typed.recovery) ? typed.recovery : void 0
|
|
2810
|
+
);
|
|
1860
2811
|
}
|
|
1861
2812
|
/**
|
|
1862
2813
|
* Emit `REQUEST_DEVICE_PERMISSION` and await the host's
|
|
@@ -1871,29 +2822,33 @@ var _TrezorAdapter = class _TrezorAdapter {
|
|
|
1871
2822
|
* - no `connectId` → environment-level check (scan / picker)
|
|
1872
2823
|
* - with `connectId` → device-level check (per-call gating)
|
|
1873
2824
|
*/
|
|
1874
|
-
async _ensureDevicePermission(connectId, deviceId) {
|
|
2825
|
+
async _ensureDevicePermission(connectId, deviceId, requestedTransport) {
|
|
1875
2826
|
if (!this._emitter.listenerCount(UI_REQUEST.REQUEST_DEVICE_PERMISSION)) {
|
|
1876
2827
|
return;
|
|
1877
2828
|
}
|
|
1878
|
-
const transportType = this.activeTransport ?? "usb";
|
|
2829
|
+
const transportType = requestedTransport ?? this.activeTransport ?? "usb";
|
|
2830
|
+
const operationId = this._activeOperationId();
|
|
1879
2831
|
const waitPromise = this._uiRegistry.wait(
|
|
1880
2832
|
UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
1881
|
-
{ timeoutMs: 6e4 }
|
|
2833
|
+
{ timeoutMs: 6e4, operationId }
|
|
1882
2834
|
);
|
|
1883
2835
|
this._emitter.emit(UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
1884
2836
|
type: UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
1885
|
-
payload: { transportType, connectId, deviceId }
|
|
2837
|
+
payload: { transportType, connectId, deviceId, operationId }
|
|
1886
2838
|
});
|
|
1887
|
-
const { granted } = await waitPromise;
|
|
2839
|
+
const { granted, reason } = await waitPromise;
|
|
1888
2840
|
if (!granted) {
|
|
1889
2841
|
throw Object.assign(new Error("Device permission denied"), {
|
|
1890
|
-
code: HardwareErrorCode.DevicePermissionDenied
|
|
2842
|
+
code: HardwareErrorCode.DevicePermissionDenied,
|
|
2843
|
+
...reason ? { params: { permissionDeniedReason: reason } } : {}
|
|
1891
2844
|
});
|
|
1892
2845
|
}
|
|
1893
2846
|
}
|
|
1894
2847
|
};
|
|
1895
2848
|
// Keys are matched after normalizeLogKey, so snake_case/camelCase both hit.
|
|
1896
2849
|
_TrezorAdapter._sensitiveLogKeys = /* @__PURE__ */ new Set([
|
|
2850
|
+
"extra",
|
|
2851
|
+
"knownconnections",
|
|
1897
2852
|
"credential",
|
|
1898
2853
|
"credentials",
|
|
1899
2854
|
"entropy",
|