@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +26 -8
  2. package/README.md +1 -1
  3. package/lib/api/applyFlags.js +1 -0
  4. package/lib/api/applySettings.js +1 -0
  5. package/lib/api/authenticateDevice.js +1 -0
  6. package/lib/api/backupDevice.js +1 -0
  7. package/lib/api/bitcoin/signtxVerify.d.ts +10 -4
  8. package/lib/api/bitcoin/signtxVerify.js +23 -72
  9. package/lib/api/bleUnpair.js +1 -0
  10. package/lib/api/changeLanguage.js +1 -0
  11. package/lib/api/changePin.js +1 -0
  12. package/lib/api/changeWipeCode.js +1 -0
  13. package/lib/api/composeTransaction.js +15 -6
  14. package/lib/api/ethereum/api/ethereumSignTypedData.js +1 -1
  15. package/lib/api/firmware/uploadFirmware.js +5 -0
  16. package/lib/api/loadDevice.js +1 -0
  17. package/lib/api/recoveryDevice.js +1 -0
  18. package/lib/api/resetDevice.js +1 -0
  19. package/lib/api/setBrightness.js +1 -0
  20. package/lib/api/setBusy.js +1 -0
  21. package/lib/api/signTransaction.js +19 -6
  22. package/lib/api/wipeDevice.js +1 -0
  23. package/lib/constants/errors.d.ts +1 -0
  24. package/lib/constants/errors.js +1 -0
  25. package/lib/core/AbstractMethod.js +1 -1
  26. package/lib/core/onCallFirmwareUpdate.js +1 -3
  27. package/lib/data/DataManager.d.ts +2 -1
  28. package/lib/data/DataManager.js +7 -6
  29. package/lib/data/firmwareInfo.d.ts +1 -1
  30. package/lib/data/firmwareInfo.js +5 -3
  31. package/lib/data/version.d.ts +1 -1
  32. package/lib/data/version.js +1 -1
  33. package/lib/device/Device.d.ts +10 -10
  34. package/lib/device/Device.js +53 -62
  35. package/lib/device/DeviceCurrentSession.d.ts +38 -1
  36. package/lib/device/DeviceCurrentSession.js +28 -4
  37. package/lib/device/DeviceList.js +23 -39
  38. package/lib/device/TransportList.d.ts +2 -2
  39. package/lib/device/thp/index.d.ts +3 -0
  40. package/lib/device/thp/index.js +11 -0
  41. package/lib/device/workflow/handshake.d.ts +10 -0
  42. package/lib/device/workflow/handshake.js +57 -0
  43. package/lib/events/index.d.ts +1 -0
  44. package/lib/events/ui-request.d.ts +6 -1
  45. package/lib/events/ui-request.js +1 -0
  46. package/lib/tsconfig.lib.tsbuildinfo +1 -1
  47. package/lib/utils/assetUtils.d.ts +2 -1
  48. package/lib/utils/assetUtils.js +2 -1
  49. package/lib/utils/firmwareReleaseConfigUtils.d.ts +7 -0
  50. package/lib/utils/firmwareReleaseConfigUtils.js +83 -0
  51. package/package.json +21 -18
@@ -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
- type DeviceLifecycle = typeof DEVICE.CONNECT | typeof DEVICE.CONNECT_UNACQUIRED | typeof DEVICE.DISCONNECT | typeof DEVICE.CHANGED;
52
- type DeviceLifecycleListener = (lifecycle: DeviceLifecycle) => void;
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
- private emitLifecycle;
150
+ readonly lifecycle: TypedEmitter<DeviceLifecycleEvents>;
151
151
  private sessionDfd?;
152
- handshakeFinished: boolean;
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
- handshake(delay?: number): Promise<void>;
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>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Device = exports.GET_FEATURES_TIMEOUT_REACT_NATIVE = exports.GET_FEATURES_TIMEOUT = exports.CANCEL_TIMEOUT = void 0;
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
- emitLifecycle;
78
+ lifecycle = new utils_1.TypedEmitter();
80
79
  sessionDfd;
81
- handshakeFinished = false;
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 handshake(delay) {
179
- if (delay) {
180
- await (0, utils_1.resolveAfter)(501 + delay);
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
- while (true) {
183
- if (this.isUsedElsewhere()) {
184
- this.emitLifecycle(events_1.DEVICE.CONNECT_UNACQUIRED);
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
- else {
187
- try {
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.emitLifecycle(events_1.DEVICE.CHANGED);
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.emitLifecycle(events_1.DEVICE.CONNECT);
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
- if (fn) {
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.emitLifecycle(events_1.DEVICE.DISCONNECT);
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
- private call;
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, timeout) {
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
- timeout,
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', {});
@@ -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(async () => {
101
- if (this.devices.includes(device)) {
102
- await device.handshake(penalty);
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
- await waitForDevicesPromise;
135
+ if (pendingTransportEvent && descriptors.length) {
136
+ await this.waitForDevices(transport, signal);
137
+ }
141
138
  }
142
- waitForDevices(transport, descriptors, signal) {
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
- const remaining = descriptors.slice();
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,3 @@
1
+ import type { Device } from '../Device';
2
+ export declare const getThpChannel: (_device: Device, _withInteraction?: boolean) => Promise<void>;
3
+ //# sourceMappingURL=index.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
@@ -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
  };
@@ -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',