@trezor/connect 9.6.1-beta.2 → 9.6.2-beta.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/CHANGELOG.md +26 -8
- package/README.md +1 -1
- package/lib/api/applyFlags.js +1 -0
- package/lib/api/applySettings.js +1 -0
- package/lib/api/authenticateDevice.js +1 -0
- package/lib/api/backupDevice.js +1 -0
- package/lib/api/bitcoin/signtxVerify.d.ts +10 -4
- package/lib/api/bitcoin/signtxVerify.js +23 -72
- package/lib/api/bleUnpair.js +1 -0
- package/lib/api/changeLanguage.js +1 -0
- package/lib/api/changePin.js +1 -0
- package/lib/api/changeWipeCode.js +1 -0
- package/lib/api/composeTransaction.js +15 -6
- package/lib/api/ethereum/api/ethereumSignTypedData.js +1 -1
- package/lib/api/firmware/uploadFirmware.js +5 -0
- package/lib/api/loadDevice.js +1 -0
- package/lib/api/recoveryDevice.js +1 -0
- package/lib/api/resetDevice.js +1 -0
- package/lib/api/setBrightness.js +1 -0
- package/lib/api/setBusy.js +1 -0
- package/lib/api/signTransaction.js +19 -6
- package/lib/api/wipeDevice.js +1 -0
- package/lib/constants/errors.d.ts +1 -0
- package/lib/constants/errors.js +1 -0
- package/lib/core/AbstractMethod.js +1 -1
- package/lib/core/onCallFirmwareUpdate.js +1 -3
- package/lib/data/DataManager.d.ts +2 -1
- package/lib/data/DataManager.js +7 -6
- package/lib/data/firmwareInfo.d.ts +1 -1
- package/lib/data/firmwareInfo.js +5 -3
- package/lib/data/version.d.ts +1 -1
- package/lib/data/version.js +1 -1
- package/lib/device/Device.d.ts +10 -10
- package/lib/device/Device.js +53 -62
- package/lib/device/DeviceCurrentSession.d.ts +38 -1
- package/lib/device/DeviceCurrentSession.js +28 -4
- package/lib/device/DeviceList.js +23 -39
- package/lib/device/TransportList.d.ts +2 -2
- package/lib/device/thp/index.d.ts +3 -0
- package/lib/device/thp/index.js +11 -0
- package/lib/device/workflow/handshake.d.ts +10 -0
- package/lib/device/workflow/handshake.js +57 -0
- package/lib/events/index.d.ts +1 -0
- package/lib/events/ui-request.d.ts +6 -1
- package/lib/events/ui-request.js +1 -0
- package/lib/tsconfig.lib.tsbuildinfo +1 -1
- package/lib/utils/assetUtils.d.ts +2 -1
- package/lib/utils/assetUtils.js +2 -1
- package/lib/utils/firmwareReleaseConfigUtils.d.ts +7 -0
- package/lib/utils/firmwareReleaseConfigUtils.js +83 -0
- package/package.json +21 -18
package/lib/device/Device.d.ts
CHANGED
|
@@ -14,9 +14,6 @@ type RunOptions = {
|
|
|
14
14
|
skipFirmwareChecks?: boolean;
|
|
15
15
|
skipLanguageChecks?: boolean;
|
|
16
16
|
};
|
|
17
|
-
export declare const CANCEL_TIMEOUT = 1000;
|
|
18
|
-
export declare const GET_FEATURES_TIMEOUT = 3000;
|
|
19
|
-
export declare const GET_FEATURES_TIMEOUT_REACT_NATIVE = 20000;
|
|
20
17
|
type Result<T> = {
|
|
21
18
|
success: true;
|
|
22
19
|
payload: T;
|
|
@@ -48,13 +45,16 @@ export interface DeviceEvents {
|
|
|
48
45
|
};
|
|
49
46
|
[DEVICE.THP_CREDENTIALS_CHANGED]: DeviceThpCredentialsChangedPayload;
|
|
50
47
|
}
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
interface DeviceLifecycleEvents {
|
|
49
|
+
[DEVICE.CONNECT]: void;
|
|
50
|
+
[DEVICE.CONNECT_UNACQUIRED]: void;
|
|
51
|
+
[DEVICE.CHANGED]: void;
|
|
52
|
+
[DEVICE.DISCONNECT]: void;
|
|
53
|
+
}
|
|
53
54
|
type DeviceParams = {
|
|
54
55
|
id: DeviceUniquePath;
|
|
55
56
|
transport: Transport;
|
|
56
57
|
descriptor: Descriptor;
|
|
57
|
-
listener: DeviceLifecycleListener;
|
|
58
58
|
};
|
|
59
59
|
export declare class Device extends TypedEmitter<DeviceEvents> {
|
|
60
60
|
readonly transport: Transport;
|
|
@@ -147,17 +147,17 @@ export declare class Device extends TypedEmitter<DeviceEvents> {
|
|
|
147
147
|
private availableTranslations;
|
|
148
148
|
private authenticityChecks;
|
|
149
149
|
private readonly uniquePath;
|
|
150
|
-
|
|
150
|
+
readonly lifecycle: TypedEmitter<DeviceLifecycleEvents>;
|
|
151
151
|
private sessionDfd?;
|
|
152
|
-
|
|
153
|
-
constructor({ id, transport, descriptor, listener }: DeviceParams);
|
|
152
|
+
constructor({ id, transport, descriptor }: DeviceParams);
|
|
154
153
|
private readonly onTransportStopped;
|
|
155
154
|
private readonly onTransportDeviceEvent;
|
|
156
155
|
private getSessionChangePromise;
|
|
157
156
|
private waitAndCompareSession;
|
|
158
157
|
acquire(): import("@trezor/transport/lib/types").AsyncResultWithTypedError<Session, "Network request failed" | "Wrong result type." | "device disconnected during action" | "unexpected error" | "Aborted by timeout" | "Aborted by signal" | "This transport can not be used in this environment" | "device not found" | "Unable to open device" | "wrong previous session" | "LIBUSB_ERROR_ACCESS">;
|
|
159
158
|
release(): import("@trezor/transport/lib/types").AsyncResultWithTypedError<null, "Network request failed" | "Wrong result type." | "device disconnected during action" | "unexpected error" | "session not found" | "Aborted by timeout" | "Aborted by signal" | "This transport can not be used in this environment" | "device not found" | "Unable to open device" | "wrong previous session"> | undefined;
|
|
160
|
-
|
|
159
|
+
setupThp(): Promise<void>;
|
|
160
|
+
handshake(): Promise<boolean>;
|
|
161
161
|
private updateDescriptor;
|
|
162
162
|
run(fn?: () => Promise<void>, options?: RunOptions): Promise<void>;
|
|
163
163
|
interrupt(reason: Error): Promise<void>;
|
package/lib/device/Device.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Device =
|
|
3
|
+
exports.Device = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const device_utils_1 = require("@trezor/device-utils");
|
|
6
6
|
const protocol_1 = require("@trezor/protocol");
|
|
@@ -10,6 +10,7 @@ const DeviceCommands_1 = require("./DeviceCommands");
|
|
|
10
10
|
const constants_1 = require("../constants");
|
|
11
11
|
const DeviceCurrentSession_1 = require("./DeviceCurrentSession");
|
|
12
12
|
const checkFirmwareRevision_1 = require("./checkFirmwareRevision");
|
|
13
|
+
const thp_1 = require("./thp");
|
|
13
14
|
const firmware_1 = require("../api/firmware");
|
|
14
15
|
const DataManager_1 = require("../data/DataManager");
|
|
15
16
|
const coinInfo_1 = require("../data/coinInfo");
|
|
@@ -17,12 +18,10 @@ const firmwareInfo_1 = require("../data/firmwareInfo");
|
|
|
17
18
|
const getLanguage_1 = require("../data/getLanguage");
|
|
18
19
|
const events_1 = require("../events");
|
|
19
20
|
const types_1 = require("../types");
|
|
21
|
+
const handshake_1 = require("./workflow/handshake");
|
|
20
22
|
const debug_1 = require("../utils/debug");
|
|
21
23
|
const deviceFeaturesUtils_1 = require("../utils/deviceFeaturesUtils");
|
|
22
24
|
const _log = (0, debug_1.initLog)('Device');
|
|
23
|
-
exports.CANCEL_TIMEOUT = 1_000;
|
|
24
|
-
exports.GET_FEATURES_TIMEOUT = 3_000;
|
|
25
|
-
exports.GET_FEATURES_TIMEOUT_REACT_NATIVE = 20_000;
|
|
26
25
|
class Device extends utils_1.TypedEmitter {
|
|
27
26
|
transport;
|
|
28
27
|
transportPath;
|
|
@@ -76,12 +75,10 @@ class Device extends utils_1.TypedEmitter {
|
|
|
76
75
|
firmwareHash: null,
|
|
77
76
|
};
|
|
78
77
|
uniquePath;
|
|
79
|
-
|
|
78
|
+
lifecycle = new utils_1.TypedEmitter();
|
|
80
79
|
sessionDfd;
|
|
81
|
-
|
|
82
|
-
constructor({ id, transport, descriptor, listener }) {
|
|
80
|
+
constructor({ id, transport, descriptor }) {
|
|
83
81
|
super();
|
|
84
|
-
this.emitLifecycle = listener;
|
|
85
82
|
this._protocol = protocol_1.v1;
|
|
86
83
|
this.uniquePath = id;
|
|
87
84
|
this.transport = transport;
|
|
@@ -175,49 +172,47 @@ class Device extends utils_1.TypedEmitter {
|
|
|
175
172
|
});
|
|
176
173
|
return this.releasePromise;
|
|
177
174
|
}
|
|
178
|
-
async
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
async setupThp() {
|
|
176
|
+
_log.info('Setup THP device');
|
|
177
|
+
this._protocol = protocol_1.v2;
|
|
178
|
+
if (this.transport.name === 'BridgeTransport' &&
|
|
179
|
+
!utils_1.versionUtils.isNewerOrEqual(this.transport.version, '3.0.0')) {
|
|
180
|
+
this.unreadableError = 'THP incompatible with bridge ' + this.transport.version;
|
|
181
181
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
this.
|
|
182
|
+
else {
|
|
183
|
+
try {
|
|
184
|
+
await this.transport.loadMessages('thp', protocol_1.thp.getProtobufDefinitions);
|
|
185
|
+
this.thp = new protocol_1.thp.ThpState();
|
|
185
186
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
await this.run();
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
_log.warn(`device.run error.message: ${error.message}, code: ${error.code}`);
|
|
192
|
-
if (error.code === 'Device_NotFound' ||
|
|
193
|
-
error.code === 'Device_Disconnected' ||
|
|
194
|
-
error.message === transport_1.TRANSPORT_ERROR.DEVICE_NOT_FOUND ||
|
|
195
|
-
error.message === transport_1.TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION ||
|
|
196
|
-
error.message === transport_1.TRANSPORT_ERROR.HTTP_ERROR) {
|
|
197
|
-
}
|
|
198
|
-
else if ((this.possibleHIDdevice &&
|
|
199
|
-
error.message === transport_1.TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE) ||
|
|
200
|
-
error.message === transport_1.TRANSPORT_ERROR.LIBUSB_ERROR_ACCESS) {
|
|
201
|
-
this.unreadableError = error.message;
|
|
202
|
-
this.emitLifecycle(events_1.DEVICE.CONNECT_UNACQUIRED);
|
|
203
|
-
}
|
|
204
|
-
else if (error.message === transport_1.TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE ||
|
|
205
|
-
error.message === transport_1.TRANSPORT_ERROR.UNEXPECTED_ERROR ||
|
|
206
|
-
error.message === transport_1.TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS ||
|
|
207
|
-
error.code === 'Device_UsedElsewhere' ||
|
|
208
|
-
error.code === 'Device_InitializeFailed') {
|
|
209
|
-
this.emitLifecycle(events_1.DEVICE.CONNECT_UNACQUIRED);
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
await (0, utils_1.resolveAfter)(501);
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
this.unreadableError = error.message;
|
|
216
189
|
}
|
|
217
|
-
this.handshakeFinished = true;
|
|
218
|
-
return;
|
|
219
190
|
}
|
|
220
191
|
}
|
|
192
|
+
async handshake() {
|
|
193
|
+
if (this.isUsedElsewhere()) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
await this.run();
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
_log.warn(`device.run error.message: ${error.message}, code: ${error.code}`);
|
|
201
|
+
if (error.code === 'Device_NotFound' ||
|
|
202
|
+
error.code === 'Device_Disconnected' ||
|
|
203
|
+
error.message === transport_1.TRANSPORT_ERROR.DEVICE_NOT_FOUND ||
|
|
204
|
+
error.message === transport_1.TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION ||
|
|
205
|
+
error.message === transport_1.TRANSPORT_ERROR.HTTP_ERROR) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
if ((this.possibleHIDdevice &&
|
|
209
|
+
error.message === transport_1.TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE) ||
|
|
210
|
+
error.message === transport_1.TRANSPORT_ERROR.LIBUSB_ERROR_ACCESS) {
|
|
211
|
+
this.unreadableError = error.message;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
221
216
|
async updateDescriptor(descriptor) {
|
|
222
217
|
this.sessionDfd?.resolve(descriptor.session);
|
|
223
218
|
await Promise.all([this.acquirePromise, this.releasePromise]);
|
|
@@ -227,7 +222,7 @@ class Device extends utils_1.TypedEmitter {
|
|
|
227
222
|
if (!descriptor.session) {
|
|
228
223
|
this.keepTransportSession = false;
|
|
229
224
|
}
|
|
230
|
-
this.
|
|
225
|
+
this.lifecycle.emit(events_1.DEVICE.CHANGED);
|
|
231
226
|
}
|
|
232
227
|
run(fn, options = {}) {
|
|
233
228
|
if (this.runPromise) {
|
|
@@ -255,7 +250,7 @@ class Device extends utils_1.TypedEmitter {
|
|
|
255
250
|
})
|
|
256
251
|
.then(() => {
|
|
257
252
|
if (wasUnacquired && !this.isUnacquired()) {
|
|
258
|
-
this.
|
|
253
|
+
this.lifecycle.emit(events_1.DEVICE.CONNECT);
|
|
259
254
|
}
|
|
260
255
|
});
|
|
261
256
|
return this.runPromise;
|
|
@@ -291,26 +286,23 @@ class Device extends utils_1.TypedEmitter {
|
|
|
291
286
|
const { staticSessionId, deriveCardano } = this.getState() || {};
|
|
292
287
|
if (acquireNeeded || !staticSessionId || (!deriveCardano && options.useCardanoDerivation)) {
|
|
293
288
|
try {
|
|
294
|
-
|
|
289
|
+
await (0, handshake_1.handshakeCancel)({ device: this, logger: _log, signal: abortSignal });
|
|
290
|
+
if (this.protocol.name === 'v2') {
|
|
291
|
+
const withInteraction = !!fn;
|
|
292
|
+
await (0, thp_1.getThpChannel)(this, withInteraction);
|
|
293
|
+
}
|
|
294
|
+
else if (fn) {
|
|
295
295
|
await this.initialize(!!options.useCardanoDerivation);
|
|
296
296
|
}
|
|
297
297
|
else {
|
|
298
|
-
const isNative = DataManager_1.DataManager.getSettings('env') === 'react-native';
|
|
299
|
-
const cancelTimeout = isNative
|
|
300
|
-
? exports.GET_FEATURES_TIMEOUT_REACT_NATIVE
|
|
301
|
-
: exports.CANCEL_TIMEOUT;
|
|
302
|
-
if (['v1', 'bridge'].includes(this.protocol.name) && !this.possibleHIDdevice) {
|
|
303
|
-
_log.debug('sending a preventive cancel on the first encounter with the device');
|
|
304
|
-
await Promise.race([
|
|
305
|
-
this.getCurrentSession().cancelCall(),
|
|
306
|
-
new Promise((_, reject) => setTimeout(reject, cancelTimeout)),
|
|
307
|
-
]).catch(() => this.acquire());
|
|
308
|
-
}
|
|
309
298
|
await this.getFeatures();
|
|
310
299
|
}
|
|
311
300
|
}
|
|
312
301
|
catch (error) {
|
|
313
302
|
_log.warn('Device._runInner error: ', error.message);
|
|
303
|
+
if (error.code === 'Device_ThpPairingTagInvalid') {
|
|
304
|
+
return Promise.reject(error);
|
|
305
|
+
}
|
|
314
306
|
return Promise.reject(constants_1.ERRORS.TypedError('Device_InitializeFailed', `Initialize failed: ${error.message}${error.code ? `, code: ${error.code}` : ''}`));
|
|
315
307
|
}
|
|
316
308
|
}
|
|
@@ -632,8 +624,7 @@ class Device extends utils_1.TypedEmitter {
|
|
|
632
624
|
this.transport.releaseSync(this.sessionAcquired);
|
|
633
625
|
this.sessionAcquired = null;
|
|
634
626
|
}
|
|
635
|
-
this.
|
|
636
|
-
this.emitLifecycle = () => { };
|
|
627
|
+
this.lifecycle.emit(events_1.DEVICE.DISCONNECT);
|
|
637
628
|
return this.interrupt(constants_1.ERRORS.TypedError('Device_Disconnected'));
|
|
638
629
|
}
|
|
639
630
|
isBootloader() {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { MessagesSchema as Messages } from '@trezor/protobuf';
|
|
2
2
|
import { MessageResponse, Session, Transport } from '@trezor/transport';
|
|
3
3
|
import { Device } from './Device';
|
|
4
|
+
type AbortableOptions = {
|
|
5
|
+
timeout?: number;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
};
|
|
4
8
|
export interface TypedCallProvider {
|
|
5
9
|
typedCall: Messages.TypedCall;
|
|
6
10
|
cancelCall: DeviceCurrentSession['cancelCall'];
|
|
7
11
|
isDisposed: () => boolean;
|
|
12
|
+
call: DeviceCurrentSession['call'];
|
|
13
|
+
send: DeviceCurrentSession['send'];
|
|
14
|
+
receive: DeviceCurrentSession['receive'];
|
|
8
15
|
}
|
|
9
16
|
export declare class DeviceCurrentSession implements TypedCallProvider {
|
|
10
17
|
private readonly device;
|
|
@@ -465,6 +472,15 @@ export declare class DeviceCurrentSession implements TypedCallProvider {
|
|
|
465
472
|
} | {
|
|
466
473
|
type: "ECDHSessionKey";
|
|
467
474
|
message: import("@trezor/protobuf/lib/messages").ECDHSessionKey;
|
|
475
|
+
} | {
|
|
476
|
+
type: "DebugLinkGetPairingInfo";
|
|
477
|
+
message: import("@trezor/protobuf/lib/messages").DebugLinkGetPairingInfo;
|
|
478
|
+
} | {
|
|
479
|
+
type: "DebugLinkPairingInfo";
|
|
480
|
+
message: import("@trezor/protobuf/lib/messages").DebugLinkPairingInfo;
|
|
481
|
+
} | {
|
|
482
|
+
type: "DebugLinkToggleThpPairingDialog";
|
|
483
|
+
message: import("@trezor/protobuf/lib/messages").DebugLinkToggleThpPairingDialog;
|
|
468
484
|
} | {
|
|
469
485
|
type: "DebugLinkResetDebugEvents";
|
|
470
486
|
message: import("@trezor/protobuf/lib/messages").DebugLinkResetDebugEvents;
|
|
@@ -819,7 +835,27 @@ export declare class DeviceCurrentSession implements TypedCallProvider {
|
|
|
819
835
|
}>;
|
|
820
836
|
private needCancelWorkaround;
|
|
821
837
|
private callLoop;
|
|
822
|
-
|
|
838
|
+
call<T extends Messages.MessageKey>(name: T, data: Messages.MessagePayload<T>, options?: AbortableOptions): Promise<{
|
|
839
|
+
success: false;
|
|
840
|
+
error: Error;
|
|
841
|
+
} | {
|
|
842
|
+
success: true;
|
|
843
|
+
payload: MessageResponse;
|
|
844
|
+
}>;
|
|
845
|
+
send<T extends Messages.MessageKey>(name: T, data: Messages.MessagePayload<T>, options?: AbortableOptions): Promise<{
|
|
846
|
+
success: false;
|
|
847
|
+
error: Error;
|
|
848
|
+
} | {
|
|
849
|
+
success: true;
|
|
850
|
+
payload: undefined;
|
|
851
|
+
}>;
|
|
852
|
+
receive(options?: AbortableOptions): Promise<{
|
|
853
|
+
success: false;
|
|
854
|
+
error: Error;
|
|
855
|
+
} | {
|
|
856
|
+
success: true;
|
|
857
|
+
payload: MessageResponse;
|
|
858
|
+
}>;
|
|
823
859
|
cancelCall(): Promise<{
|
|
824
860
|
success: false;
|
|
825
861
|
error: Error;
|
|
@@ -829,4 +865,5 @@ export declare class DeviceCurrentSession implements TypedCallProvider {
|
|
|
829
865
|
}>;
|
|
830
866
|
abort(reason: Error): Promise<void>;
|
|
831
867
|
}
|
|
868
|
+
export {};
|
|
832
869
|
//# sourceMappingURL=DeviceCurrentSession.d.ts.map
|
|
@@ -99,7 +99,7 @@ class DeviceCurrentSession {
|
|
|
99
99
|
let pinUnlocked = false;
|
|
100
100
|
while (true) {
|
|
101
101
|
const timeout = name === 'GetFeatures' ? getFeaturesTimeout() : undefined;
|
|
102
|
-
const callPromise = this.call(name, data, timeout);
|
|
102
|
+
const callPromise = this.call(name, data, { timeout });
|
|
103
103
|
const [abortedDuringCall, response] = await Promise.race([
|
|
104
104
|
callPromise.then(res => [false, res]),
|
|
105
105
|
abortPromise.then(res => [true, res]),
|
|
@@ -206,7 +206,7 @@ class DeviceCurrentSession {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
|
-
async call(name, data,
|
|
209
|
+
async call(name, data, options = {}) {
|
|
210
210
|
if (this.disposed)
|
|
211
211
|
return Promise.resolve(error(this.disposed));
|
|
212
212
|
logger.debug('Sending', name, filterForLog(name, data));
|
|
@@ -216,7 +216,7 @@ class DeviceCurrentSession {
|
|
|
216
216
|
session: this.session,
|
|
217
217
|
protocol: this.device.protocol,
|
|
218
218
|
thpState: this.device.getThpState(),
|
|
219
|
-
|
|
219
|
+
...options,
|
|
220
220
|
});
|
|
221
221
|
if (result.success) {
|
|
222
222
|
const { type, message } = result.payload;
|
|
@@ -225,7 +225,31 @@ class DeviceCurrentSession {
|
|
|
225
225
|
else {
|
|
226
226
|
logger.warn('Received transport error', result.error, result.message);
|
|
227
227
|
}
|
|
228
|
-
return result.success ? success(result.payload) : fail(result.error);
|
|
228
|
+
return result.success ? success(result.payload) : fail(result.message || result.error);
|
|
229
|
+
}
|
|
230
|
+
async send(name, data, options = {}) {
|
|
231
|
+
if (this.disposed)
|
|
232
|
+
return Promise.resolve(error(this.disposed));
|
|
233
|
+
const result = await this.transport.send({
|
|
234
|
+
name,
|
|
235
|
+
data,
|
|
236
|
+
session: this.session,
|
|
237
|
+
protocol: this.device.protocol,
|
|
238
|
+
thpState: this.device.getThpState(),
|
|
239
|
+
...options,
|
|
240
|
+
});
|
|
241
|
+
return result.success ? success(result.payload) : fail(result.message || result.error);
|
|
242
|
+
}
|
|
243
|
+
async receive(options = {}) {
|
|
244
|
+
if (this.disposed)
|
|
245
|
+
return Promise.resolve(error(this.disposed));
|
|
246
|
+
const result = await this.transport.receive({
|
|
247
|
+
session: this.session,
|
|
248
|
+
protocol: this.device.protocol,
|
|
249
|
+
thpState: this.device.getThpState(),
|
|
250
|
+
...options,
|
|
251
|
+
});
|
|
252
|
+
return result.success ? success(result.payload) : fail(result.message || result.error);
|
|
229
253
|
}
|
|
230
254
|
cancelCall() {
|
|
231
255
|
return this.call('Cancel', {});
|
package/lib/device/DeviceList.js
CHANGED
|
@@ -79,29 +79,27 @@ class DeviceList extends utils_1.TypedEmitter {
|
|
|
79
79
|
id: manifest?.appName || manifest?.appUrl || 'unknown app',
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
onDeviceConnected(descriptor, transport) {
|
|
82
|
+
async onDeviceConnected(descriptor, transport) {
|
|
83
83
|
const id = (this.deviceCounter++).toString(16).slice(-8);
|
|
84
|
-
const device = new Device_1.Device({
|
|
85
|
-
id: (0, types_1.DeviceUniquePath)(id),
|
|
86
|
-
transport,
|
|
87
|
-
descriptor,
|
|
88
|
-
listener: lifecycle => {
|
|
89
|
-
if (lifecycle === events_1.DEVICE.DISCONNECT) {
|
|
90
|
-
this.authPenaltyManager.remove(device);
|
|
91
|
-
const index = this.devices.indexOf(device);
|
|
92
|
-
if (index >= 0)
|
|
93
|
-
this.devices.splice(index, 1);
|
|
94
|
-
}
|
|
95
|
-
this.emit(lifecycle, device);
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
this.devices.push(device);
|
|
84
|
+
const device = new Device_1.Device({ id: (0, types_1.DeviceUniquePath)(id), transport, descriptor });
|
|
99
85
|
const penalty = this.authPenaltyManager.get();
|
|
100
|
-
this.handshakeLock(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
const stillConnected = await this.handshakeLock(() => (0, utils_1.resolveAfter)(penalty && penalty + 501).then(() => device.handshake()));
|
|
87
|
+
if (!stillConnected) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.devices.push(device);
|
|
91
|
+
device.lifecycle.on(events_1.DEVICE.CONNECT, () => this.emit(events_1.DEVICE.CONNECT, device));
|
|
92
|
+
device.lifecycle.on(events_1.DEVICE.CHANGED, () => this.emit(events_1.DEVICE.CHANGED, device));
|
|
93
|
+
device.lifecycle.on(events_1.DEVICE.CONNECT_UNACQUIRED, () => this.emit(events_1.DEVICE.CONNECT_UNACQUIRED, device));
|
|
94
|
+
device.lifecycle.on(events_1.DEVICE.DISCONNECT, () => {
|
|
95
|
+
device.lifecycle.removeAllListeners();
|
|
96
|
+
this.authPenaltyManager.remove(device);
|
|
97
|
+
const index = this.devices.indexOf(device);
|
|
98
|
+
if (index >= 0)
|
|
99
|
+
this.devices.splice(index, 1);
|
|
100
|
+
this.emit(events_1.DEVICE.DISCONNECT, device);
|
|
104
101
|
});
|
|
102
|
+
this.emit(device.isUnacquired() ? events_1.DEVICE.CONNECT_UNACQUIRED : events_1.DEVICE.CONNECT, device);
|
|
105
103
|
}
|
|
106
104
|
getOrCreateTransportManager(apiType) {
|
|
107
105
|
if (!this.transportManagers[apiType]) {
|
|
@@ -132,38 +130,24 @@ class DeviceList extends utils_1.TypedEmitter {
|
|
|
132
130
|
throw new Error(enumerateResult.error);
|
|
133
131
|
}
|
|
134
132
|
const descriptors = enumerateResult.payload;
|
|
135
|
-
const waitForDevicesPromise = pendingTransportEvent && descriptors.length
|
|
136
|
-
? this.waitForDevices(transport, descriptors, signal)
|
|
137
|
-
: Promise.resolve();
|
|
138
133
|
transport.handleDescriptorsChange(descriptors);
|
|
139
134
|
transport.listen();
|
|
140
|
-
|
|
135
|
+
if (pendingTransportEvent && descriptors.length) {
|
|
136
|
+
await this.waitForDevices(transport, signal);
|
|
137
|
+
}
|
|
141
138
|
}
|
|
142
|
-
waitForDevices(transport,
|
|
139
|
+
waitForDevices(transport, signal) {
|
|
143
140
|
const { promise, reject, resolve } = (0, utils_1.createDeferred)();
|
|
144
141
|
const onAbort = () => reject(signal.reason);
|
|
145
142
|
signal.addEventListener('abort', onAbort);
|
|
146
143
|
const onError = (error) => reject(new Error(error));
|
|
147
144
|
transport.once(transport_1.TRANSPORT.ERROR, onError);
|
|
148
145
|
const autoResolveTransportEventTimeout = setTimeout(resolve, 10000);
|
|
149
|
-
|
|
150
|
-
const onDeviceEvent = (device) => {
|
|
151
|
-
const index = remaining.findIndex(d => d.path === device.transportPath && transport === device.transport);
|
|
152
|
-
if (index >= 0)
|
|
153
|
-
remaining.splice(index, 1);
|
|
154
|
-
if (!remaining.length)
|
|
155
|
-
resolve();
|
|
156
|
-
};
|
|
157
|
-
this.on(events_1.DEVICE.CONNECT, onDeviceEvent);
|
|
158
|
-
this.on(events_1.DEVICE.CONNECT_UNACQUIRED, onDeviceEvent);
|
|
159
|
-
this.on(events_1.DEVICE.DISCONNECT, onDeviceEvent);
|
|
146
|
+
this.handshakeLock(resolve);
|
|
160
147
|
return promise.finally(() => {
|
|
161
148
|
transport.off(transport_1.TRANSPORT.ERROR, onError);
|
|
162
149
|
signal.removeEventListener('abort', onAbort);
|
|
163
150
|
clearTimeout(autoResolveTransportEventTimeout);
|
|
164
|
-
this.off(events_1.DEVICE.CONNECT, onDeviceEvent);
|
|
165
|
-
this.off(events_1.DEVICE.CONNECT_UNACQUIRED, onDeviceEvent);
|
|
166
|
-
this.off(events_1.DEVICE.DISCONNECT, onDeviceEvent);
|
|
167
151
|
});
|
|
168
152
|
}
|
|
169
153
|
getDeviceCount() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Transport } from '@trezor/transport';
|
|
1
|
+
import { NodeUsbTransport, Transport, UdpTransport } from '@trezor/transport';
|
|
2
2
|
import type { AbstractTransportParams } from '@trezor/transport/lib/transports/abstract';
|
|
3
3
|
import { ConnectSettingsTransport } from '../types';
|
|
4
4
|
type Params = AbstractTransportParams & {
|
|
5
5
|
sessionsBackgroundUrl?: string | null;
|
|
6
6
|
};
|
|
7
|
-
export declare const createTransportList: (params: Params) => (existing: Transport[], transports?: ConnectSettingsTransport[]) => Transport[];
|
|
7
|
+
export declare const createTransportList: (params: Params) => (existing: Transport[], transports?: ConnectSettingsTransport[]) => (Transport | NodeUsbTransport | UdpTransport)[];
|
|
8
8
|
export {};
|
|
9
9
|
//# sourceMappingURL=TransportList.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getThpChannel = void 0;
|
|
4
|
+
const constants_1 = require("../../constants");
|
|
5
|
+
const getThpChannel = async (_device, _withInteraction) => {
|
|
6
|
+
await new Promise((_, reject) => {
|
|
7
|
+
reject(constants_1.ERRORS.TypedError('Device_ThpStateMissing'));
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
exports.getThpChannel = getThpChannel;
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { WorkflowContext } from '../../types/workflow';
|
|
2
|
+
import { Log } from '../../utils/debug';
|
|
3
|
+
type Context = {
|
|
4
|
+
device: WorkflowContext['device'];
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
logger?: Log;
|
|
7
|
+
};
|
|
8
|
+
export declare const handshakeCancel: ({ device, logger, signal }: Context) => Promise<void>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=handshake.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handshakeCancel = void 0;
|
|
4
|
+
const protocol_1 = require("@trezor/protocol");
|
|
5
|
+
const transport_1 = require("@trezor/transport");
|
|
6
|
+
const utils_1 = require("@trezor/utils");
|
|
7
|
+
const DataManager_1 = require("../../data/DataManager");
|
|
8
|
+
const CANCEL_TIMEOUT = 1_000;
|
|
9
|
+
const CANCEL_TIMEOUT_REACT_NATIVE = 20_000;
|
|
10
|
+
const ATTEMPTS_LIMIT = 10;
|
|
11
|
+
const isLegacyBridge = (transport) => transport.name === 'BridgeTransport' &&
|
|
12
|
+
!utils_1.versionUtils.isNewerOrEqual(transport.version, '3.0.0');
|
|
13
|
+
const handshakeCancel = async ({ device, logger, signal }) => {
|
|
14
|
+
if (device.features || device.getThpState()?.properties) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const isNative = DataManager_1.DataManager.getSettings('env') === 'react-native';
|
|
18
|
+
const cancelTimeout = isNative ? CANCEL_TIMEOUT_REACT_NATIVE : CANCEL_TIMEOUT;
|
|
19
|
+
logger?.debug('handshake Cancel start');
|
|
20
|
+
const send = await device
|
|
21
|
+
.getCurrentSession()
|
|
22
|
+
.send('Cancel', {}, { signal, timeout: cancelTimeout });
|
|
23
|
+
if (!send.success) {
|
|
24
|
+
logger?.debug(`handshake Cancel send error ${send.error}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
logger?.debug('handshake Cancel sent');
|
|
28
|
+
for (let attempt = 0; attempt < ATTEMPTS_LIMIT; ++attempt) {
|
|
29
|
+
logger?.debug(`handshake Cancel read attempt ${attempt}`);
|
|
30
|
+
const result = await device.getCurrentSession().receive({
|
|
31
|
+
signal,
|
|
32
|
+
timeout: cancelTimeout,
|
|
33
|
+
});
|
|
34
|
+
if (!result.success && result.error.message === transport_1.TRANSPORT_ERROR.ABORTED_BY_TIMEOUT) {
|
|
35
|
+
if (isLegacyBridge(device.transport)) {
|
|
36
|
+
await (0, utils_1.resolveAfter)(501);
|
|
37
|
+
}
|
|
38
|
+
await device.acquire();
|
|
39
|
+
}
|
|
40
|
+
if (!result.success && result.error.message !== protocol_1.PROTOCOL_MALFORMED) {
|
|
41
|
+
logger?.debug(`handshake Cancel read error: ${result.error}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
else if (result.success) {
|
|
45
|
+
logger?.debug(`handshake Cancel read success: ${result.payload.type}`);
|
|
46
|
+
if (result.payload.type === 'Failure' &&
|
|
47
|
+
result.payload.message.code === 'Failure_InvalidProtocol') {
|
|
48
|
+
logger?.debug(`handshake Cancel protocol v2 detected`);
|
|
49
|
+
await device.setupThp();
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
logger?.debug(`handshake Cancel attempts limit reached`);
|
|
55
|
+
};
|
|
56
|
+
exports.handshakeCancel = handshakeCancel;
|
|
57
|
+
//# sourceMappingURL=handshake.js.map
|
package/lib/events/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare const UI: {
|
|
|
35
35
|
readonly FIRMWARE_NOT_COMPATIBLE: "ui-device_firmware_not_compatible";
|
|
36
36
|
readonly FIRMWARE_NOT_INSTALLED: "ui-device_firmware_not_installed";
|
|
37
37
|
readonly FIRMWARE_PROGRESS: "ui-firmware-progress";
|
|
38
|
+
readonly FIRMWARE_PROGRESS_UNEXPECTED_DELAY: "ui-firmware-progress-unexpected-delay";
|
|
38
39
|
readonly FIRMWARE_RECONNECT: "ui-firmware_reconnect";
|
|
39
40
|
readonly FIRMWARE_DISCONNECT: "ui-firmware_disconnect";
|
|
40
41
|
readonly FIRMWARE_DOWNLOADED: "ui-firmware_downloaded";
|
|
@@ -20,6 +20,7 @@ export declare const UI_REQUEST: {
|
|
|
20
20
|
readonly FIRMWARE_NOT_COMPATIBLE: "ui-device_firmware_not_compatible";
|
|
21
21
|
readonly FIRMWARE_NOT_INSTALLED: "ui-device_firmware_not_installed";
|
|
22
22
|
readonly FIRMWARE_PROGRESS: "ui-firmware-progress";
|
|
23
|
+
readonly FIRMWARE_PROGRESS_UNEXPECTED_DELAY: "ui-firmware-progress-unexpected-delay";
|
|
23
24
|
readonly FIRMWARE_RECONNECT: "ui-firmware_reconnect";
|
|
24
25
|
readonly FIRMWARE_DISCONNECT: "ui-firmware_disconnect";
|
|
25
26
|
readonly FIRMWARE_DOWNLOADED: "ui-firmware_downloaded";
|
|
@@ -232,6 +233,10 @@ export interface FirmwareProgress {
|
|
|
232
233
|
progress: number;
|
|
233
234
|
};
|
|
234
235
|
}
|
|
236
|
+
export interface FirmwareProgressUnexpectedDelay {
|
|
237
|
+
type: typeof UI_REQUEST.FIRMWARE_PROGRESS_UNEXPECTED_DELAY;
|
|
238
|
+
payload: {};
|
|
239
|
+
}
|
|
235
240
|
export interface FirmwareReconnect {
|
|
236
241
|
type: typeof UI_REQUEST.FIRMWARE_RECONNECT;
|
|
237
242
|
payload: {
|
|
@@ -248,7 +253,7 @@ export interface FirmwareDisconnect {
|
|
|
248
253
|
device: Device;
|
|
249
254
|
};
|
|
250
255
|
}
|
|
251
|
-
export type UiEvent = UiRequestWithoutPayload | UiRequestDeviceAction | UiRequestButton | UiRequestPermission | UiRequestConfirmation | UiRequestSelectDevice | UiRequestUnexpectedDeviceMode | UiRequestSelectAccount | UiRequestSelectFee | UiRequestThpPairing | UpdateCustomFee | BundleProgress<any> | FirmwareProgress | FirmwareException | FirmwareReconnect | FirmwareDisconnect | UiRequestAddressValidation | UiRequestSetOperation | UiRequestFirmwareDownloaded;
|
|
256
|
+
export type UiEvent = UiRequestWithoutPayload | UiRequestDeviceAction | UiRequestButton | UiRequestPermission | UiRequestConfirmation | UiRequestSelectDevice | UiRequestUnexpectedDeviceMode | UiRequestSelectAccount | UiRequestSelectFee | UiRequestThpPairing | UpdateCustomFee | BundleProgress<any> | FirmwareProgress | FirmwareProgressUnexpectedDelay | FirmwareException | FirmwareReconnect | FirmwareDisconnect | UiRequestAddressValidation | UiRequestSetOperation | UiRequestFirmwareDownloaded;
|
|
252
257
|
export type UiEventMessage = UiEvent & {
|
|
253
258
|
event: typeof UI_EVENT;
|
|
254
259
|
};
|
package/lib/events/ui-request.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.UI_REQUEST = {
|
|
|
15
15
|
FIRMWARE_NOT_COMPATIBLE: 'ui-device_firmware_not_compatible',
|
|
16
16
|
FIRMWARE_NOT_INSTALLED: 'ui-device_firmware_not_installed',
|
|
17
17
|
FIRMWARE_PROGRESS: 'ui-firmware-progress',
|
|
18
|
+
FIRMWARE_PROGRESS_UNEXPECTED_DELAY: 'ui-firmware-progress-unexpected-delay',
|
|
18
19
|
FIRMWARE_RECONNECT: 'ui-firmware_reconnect',
|
|
19
20
|
FIRMWARE_DISCONNECT: 'ui-firmware_disconnect',
|
|
20
21
|
FIRMWARE_DOWNLOADED: 'ui-firmware_downloaded',
|