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