@trezor/connect 9.6.0 → 9.6.1-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 (81) hide show
  1. package/CHANGELOG.md +36 -7
  2. package/README.md +1 -1
  3. package/lib/api/blockchainEstimateFee.js +1 -0
  4. package/lib/api/discoverAccounts.js +10 -2
  5. package/lib/api/ethereum/api/ethereumSignMessage.js +1 -1
  6. package/lib/api/ethereum/api/ethereumSignTypedData.d.ts +1 -1
  7. package/lib/api/firmware/getBinary.js +2 -2
  8. package/lib/api/getAccountInfo.d.ts +1 -1
  9. package/lib/api/getCoinInfo.d.ts +3 -3
  10. package/lib/api/getNonce.d.ts +9 -0
  11. package/lib/api/getNonce.js +15 -0
  12. package/lib/api/index.d.ts +1 -0
  13. package/lib/api/index.js +4 -2
  14. package/lib/api/nem/nemSignTx.d.ts +1 -1
  15. package/lib/api/resetDevice.d.ts +2 -3
  16. package/lib/api/resetDevice.js +47 -49
  17. package/lib/api/signMessage.js +1 -1
  18. package/lib/backend/Blockchain.d.ts +1 -0
  19. package/lib/backend/fees/BitcoinFeeLevels.d.ts +0 -1
  20. package/lib/backend/fees/EthereumFeeLevels.d.ts +0 -1
  21. package/lib/backend/fees/EthereumFeeLevels.js +8 -4
  22. package/lib/backend/fees/MiscFeeLevels.d.ts +0 -1
  23. package/lib/constants/errors.d.ts +1 -0
  24. package/lib/constants/errors.js +1 -0
  25. package/lib/core/AbstractMethod.d.ts +2 -2
  26. package/lib/core/AbstractMethod.js +0 -1
  27. package/lib/core/index.js +43 -53
  28. package/lib/core/onCallFirmwareUpdate.js +2 -2
  29. package/lib/data/coinInfo.d.ts +11 -11
  30. package/lib/data/coinInfo.js +1 -1
  31. package/lib/data/connectSettings.js +0 -3
  32. package/lib/data/defaultFeeLevels.d.ts +2 -1
  33. package/lib/data/defaultFeeLevels.js +18 -10
  34. package/lib/data/deviceAuthenticityConfig.js +4 -1
  35. package/lib/data/firmwareInfo.d.ts +1 -0
  36. package/lib/data/firmwareInfo.js +14 -1
  37. package/lib/data/version.d.ts +1 -1
  38. package/lib/data/version.js +1 -1
  39. package/lib/device/Device.d.ts +19 -20
  40. package/lib/device/Device.js +66 -112
  41. package/lib/device/DeviceCurrentSession.d.ts +441 -4027
  42. package/lib/device/DeviceCurrentSession.js +75 -44
  43. package/lib/device/DeviceList.d.ts +3 -8
  44. package/lib/device/DeviceList.js +21 -77
  45. package/lib/device/TransportManager.d.ts +2 -6
  46. package/lib/device/TransportManager.js +11 -7
  47. package/lib/device/checkFirmwareRevision.js +2 -2
  48. package/lib/device/workflow/index.d.ts +2 -0
  49. package/lib/device/workflow/index.js +6 -0
  50. package/lib/device/workflow/validateState.d.ts +3 -0
  51. package/lib/device/workflow/validateState.js +90 -0
  52. package/lib/events/device.d.ts +19 -1
  53. package/lib/events/device.js +2 -0
  54. package/lib/events/index.d.ts +3 -0
  55. package/lib/events/ui-request.d.ts +17 -3
  56. package/lib/events/ui-request.js +2 -0
  57. package/lib/events/ui-response.d.ts +11 -1
  58. package/lib/events/ui-response.js +1 -0
  59. package/lib/factory.js +1 -0
  60. package/lib/tsconfig.lib.tsbuildinfo +1 -1
  61. package/lib/types/api/applySettings.d.ts +1 -0
  62. package/lib/types/api/discoverAccounts.d.ts +17 -4
  63. package/lib/types/api/discoverAccounts.js +2 -0
  64. package/lib/types/api/getNonce.d.ts +4 -0
  65. package/lib/types/api/getNonce.js +3 -0
  66. package/lib/types/api/index.d.ts +2 -0
  67. package/lib/types/coinInfo.d.ts +6 -6
  68. package/lib/types/coinInfo.js +2 -0
  69. package/lib/types/fees.d.ts +1 -1
  70. package/lib/types/fees.js +1 -1
  71. package/lib/types/settings.d.ts +0 -1
  72. package/lib/types/workflow.d.ts +8 -0
  73. package/lib/types/workflow.js +3 -0
  74. package/lib/utils/formatUtils.d.ts +1 -0
  75. package/lib/utils/formatUtils.js +9 -1
  76. package/lib/utils/uiPromiseManager.d.ts +2 -0
  77. package/package.json +16 -16
  78. package/lib/data/downloadReleasesMetadata.d.ts +0 -7
  79. package/lib/data/downloadReleasesMetadata.js +0 -18
  80. package/lib/data/models.d.ts +0 -8
  81. package/lib/data/models.js +0 -49
@@ -15,26 +15,28 @@ const DataManager_1 = require("../data/DataManager");
15
15
  const coinInfo_1 = require("../data/coinInfo");
16
16
  const firmwareInfo_1 = require("../data/firmwareInfo");
17
17
  const getLanguage_1 = require("../data/getLanguage");
18
- const models_1 = require("../data/models");
19
18
  const events_1 = require("../events");
20
19
  const types_1 = require("../types");
21
20
  const debug_1 = require("../utils/debug");
22
21
  const deviceFeaturesUtils_1 = require("../utils/deviceFeaturesUtils");
23
- const pathUtils_1 = require("../utils/pathUtils");
24
22
  const _log = (0, debug_1.initLog)('Device');
25
23
  exports.CANCEL_TIMEOUT = 1_000;
26
24
  exports.GET_FEATURES_TIMEOUT = 3_000;
27
25
  exports.GET_FEATURES_TIMEOUT_REACT_NATIVE = 20_000;
28
26
  class Device extends utils_1.TypedEmitter {
29
27
  transport;
30
- protocol;
31
28
  transportPath;
32
29
  bluetoothProps;
33
30
  thp;
34
31
  transportDescriptorType;
35
- session;
36
- transportSessionOwner;
37
- lastAcquiredHere;
32
+ sessionAcquired;
33
+ _protocol;
34
+ get protocol() {
35
+ return this._protocol;
36
+ }
37
+ getThpState() {
38
+ return this.thp;
39
+ }
38
40
  unreadableError;
39
41
  _firmwareStatus;
40
42
  get firmwareStatus() {
@@ -48,14 +50,13 @@ class Device extends utils_1.TypedEmitter {
48
50
  get features() {
49
51
  return this._features;
50
52
  }
51
- _featuresNeedsReload = false;
53
+ wasUsedElsewhere = false;
52
54
  acquirePromise;
53
55
  releasePromise;
54
56
  runAbort;
55
57
  runPromise;
56
58
  keepTransportSession = false;
57
59
  currentSession;
58
- inconsistent = false;
59
60
  instance = 0;
60
61
  state = [];
61
62
  stateStorage = undefined;
@@ -81,16 +82,28 @@ class Device extends utils_1.TypedEmitter {
81
82
  constructor({ id, transport, descriptor, listener }) {
82
83
  super();
83
84
  this.emitLifecycle = listener;
84
- this.protocol = protocol_1.v1;
85
+ this._protocol = protocol_1.v1;
85
86
  this.uniquePath = id;
86
87
  this.transport = transport;
87
88
  this.transportPath = descriptor.path;
88
- this.transportSessionOwner = descriptor.sessionOwner;
89
89
  this.transportDescriptorType = descriptor.type;
90
90
  this.bluetoothProps = descriptor.id ? { id: descriptor.id } : undefined;
91
- this.session = descriptor.session;
92
- this.lastAcquiredHere = false;
93
- }
91
+ this.sessionAcquired = null;
92
+ transport.on(transport_1.TRANSPORT.STOPPED, this.onTransportStopped);
93
+ transport.deviceEvents.on(this.transportPath, this.onTransportDeviceEvent);
94
+ }
95
+ onTransportStopped = () => this.disconnect();
96
+ onTransportDeviceEvent = (event) => {
97
+ switch (event.type) {
98
+ case transport_1.TRANSPORT.DEVICE_SESSION_CHANGED:
99
+ return this.updateDescriptor(event.descriptor);
100
+ case transport_1.TRANSPORT.DEVICE_REQUEST_RELEASE:
101
+ return this.usedElsewhere();
102
+ case transport_1.TRANSPORT.DEVICE_DISCONNECTED: {
103
+ return this.disconnect();
104
+ }
105
+ }
106
+ };
94
107
  getSessionChangePromise() {
95
108
  if (!this.sessionDfd) {
96
109
  this.sessionDfd = (0, utils_1.createDeferred)();
@@ -123,15 +136,15 @@ class Device extends utils_1.TypedEmitter {
123
136
  }
124
137
  acquire() {
125
138
  const sessionPromise = this.getSessionChangePromise();
139
+ const previous = this.transport.getDescriptor(this.transportPath)?.session ?? null;
126
140
  this.acquirePromise = this.transport
127
- .acquire({ input: { path: this.transportPath, previous: this.session } })
141
+ .acquire({ input: { path: this.transportPath, previous } })
128
142
  .then(result => this.waitAndCompareSession(result, sessionPromise))
129
143
  .then(result => {
130
144
  if (result.success) {
131
- this.session = result.payload;
132
- this.lastAcquiredHere = true;
133
- this.currentSession?.dispose();
134
- this.currentSession = new DeviceCurrentSession_1.DeviceCurrentSession(this, this.transport, this.protocol, this.session);
145
+ this.wasUsedElsewhere = false;
146
+ this.sessionAcquired = result.payload;
147
+ this.currentSession = new DeviceCurrentSession_1.DeviceCurrentSession(this, this.transport, this.sessionAcquired);
135
148
  return result;
136
149
  }
137
150
  else {
@@ -143,19 +156,17 @@ class Device extends utils_1.TypedEmitter {
143
156
  });
144
157
  return this.acquirePromise;
145
158
  }
146
- async release() {
147
- const localSession = this.lastAcquiredHere ? this.session : null;
148
- if (!localSession || this.keepTransportSession || this.releasePromise) {
159
+ release() {
160
+ if (!this.sessionAcquired || this.keepTransportSession || this.releasePromise) {
149
161
  return;
150
162
  }
151
- await this.currentSession?.dispose();
152
163
  const sessionPromise = this.getSessionChangePromise();
153
164
  this.releasePromise = this.transport
154
- .release({ session: localSession, path: this.transportPath })
165
+ .release({ session: this.sessionAcquired, path: this.transportPath })
155
166
  .then(result => this.waitAndCompareSession(result, sessionPromise))
156
167
  .then(result => {
157
168
  if (result.success) {
158
- this.session = null;
169
+ this.sessionAcquired = null;
159
170
  }
160
171
  return result;
161
172
  })
@@ -208,17 +219,12 @@ class Device extends utils_1.TypedEmitter {
208
219
  async updateDescriptor(descriptor) {
209
220
  this.sessionDfd?.resolve(descriptor.session);
210
221
  await Promise.all([this.acquirePromise, this.releasePromise]);
211
- if (descriptor.session && descriptor.session !== this.session) {
222
+ if (descriptor.session && descriptor.session !== this.sessionAcquired) {
212
223
  this.usedElsewhere();
213
224
  }
214
225
  if (!descriptor.session) {
215
- const methodStillRunning = !this.currentSession?.isDisposed();
216
- if (methodStillRunning) {
217
- this.keepTransportSession = false;
218
- }
226
+ this.keepTransportSession = false;
219
227
  }
220
- this.session = descriptor.session;
221
- this.transportSessionOwner = descriptor.sessionOwner;
222
228
  this.emitLifecycle(events_1.DEVICE.CHANGED);
223
229
  }
224
230
  run(fn, options = {}) {
@@ -230,7 +236,7 @@ class Device extends utils_1.TypedEmitter {
230
236
  this.runAbort = new AbortController();
231
237
  const { signal } = this.runAbort;
232
238
  this.runPromise = Promise.race([
233
- this._runInner(fn, options),
239
+ this._runInner(fn, options, signal),
234
240
  new Promise((_, reject) => {
235
241
  signal.addEventListener('abort', () => reject(signal.reason));
236
242
  }),
@@ -253,7 +259,7 @@ class Device extends utils_1.TypedEmitter {
253
259
  return this.runPromise;
254
260
  }
255
261
  async interrupt(reason) {
256
- await this.currentSession?.abort(reason, true);
262
+ await this.currentSession?.abort(reason);
257
263
  this.runAbort?.abort(reason);
258
264
  await this.currentRun;
259
265
  }
@@ -261,19 +267,16 @@ class Device extends utils_1.TypedEmitter {
261
267
  return this.runPromise?.catch(() => { });
262
268
  }
263
269
  usedElsewhere() {
264
- if (!this.lastAcquiredHere) {
270
+ this.wasUsedElsewhere = true;
271
+ if (!this.sessionAcquired) {
265
272
  return;
266
273
  }
267
- this.lastAcquiredHere = false;
268
- this._featuresNeedsReload = true;
274
+ this.transport.releaseDevice(this.sessionAcquired);
275
+ this.sessionAcquired = null;
269
276
  _log.debug('interruptionFromOutside');
270
- this.currentSession?.dispose();
271
277
  this.runAbort?.abort(constants_1.ERRORS.TypedError('Device_UsedElsewhere'));
272
- if (this.session) {
273
- this.transport.releaseDevice(this.session);
274
- }
275
278
  }
276
- async _runInner(fn, options) {
279
+ async _runInner(fn, options, abortSignal) {
277
280
  if (this.releasePromise) {
278
281
  await this.releasePromise;
279
282
  }
@@ -281,6 +284,8 @@ class Device extends utils_1.TypedEmitter {
281
284
  if (acquireNeeded) {
282
285
  await this.acquire();
283
286
  }
287
+ if (abortSignal.aborted)
288
+ throw abortSignal.reason;
284
289
  const { staticSessionId, deriveCardano } = this.getState() || {};
285
290
  if (acquireNeeded || !staticSessionId || (!deriveCardano && options.useCardanoDerivation)) {
286
291
  try {
@@ -289,9 +294,6 @@ class Device extends utils_1.TypedEmitter {
289
294
  }
290
295
  else {
291
296
  const isNative = DataManager_1.DataManager.getSettings('env') === 'react-native';
292
- const getFeaturesTimeout = isNative
293
- ? exports.GET_FEATURES_TIMEOUT_REACT_NATIVE
294
- : exports.GET_FEATURES_TIMEOUT;
295
297
  const cancelTimeout = isNative
296
298
  ? exports.GET_FEATURES_TIMEOUT_REACT_NATIVE
297
299
  : exports.CANCEL_TIMEOUT;
@@ -303,29 +305,11 @@ class Device extends utils_1.TypedEmitter {
303
305
  new Promise((_, reject) => setTimeout(reject, cancelTimeout)),
304
306
  ]).catch(() => this.acquire());
305
307
  }
306
- let getFeaturesTimeoutId;
307
- await Promise.race([
308
- this.getFeatures().finally(() => clearTimeout(getFeaturesTimeoutId)),
309
- new Promise((_resolve, reject) => {
310
- getFeaturesTimeoutId = setTimeout(async () => {
311
- await this.getCurrentSession().cancelCall(false);
312
- reject(new Error('GetFeatures timeout'));
313
- }, getFeaturesTimeout);
314
- }),
315
- ]);
308
+ await this.getFeatures();
316
309
  }
317
310
  }
318
311
  catch (error) {
319
312
  _log.warn('Device._runInner error: ', error.message);
320
- if (!this.inconsistent &&
321
- (error.message === 'GetFeatures timeout' || error.message === 'Unknown message')) {
322
- this.inconsistent = true;
323
- return this._runInner(() => Promise.resolve({}), options);
324
- }
325
- if (transport_1.TRANSPORT_ERROR.ABORTED_BY_TIMEOUT === error.message) {
326
- this.unreadableError = 'Connection timeout';
327
- }
328
- this.inconsistent = true;
329
313
  return Promise.reject(constants_1.ERRORS.TypedError('Device_InitializeFailed', `Initialize failed: ${error.message}${error.code ? `, code: ${error.code}` : ''}`));
330
314
  }
331
315
  }
@@ -372,7 +356,7 @@ class Device extends utils_1.TypedEmitter {
372
356
  setInstance(instance = 0) {
373
357
  if (this.instance !== instance) {
374
358
  if (this.keepTransportSession) {
375
- this.lastAcquiredHere = false;
359
+ this.sessionAcquired = null;
376
360
  this.keepTransportSession = false;
377
361
  }
378
362
  }
@@ -398,31 +382,6 @@ class Device extends utils_1.TypedEmitter {
398
382
  this.stateStorage?.saveState(this, newState);
399
383
  }
400
384
  }
401
- async validateState(preauthorized = false) {
402
- if (!this.features)
403
- return;
404
- if (!this.features.unlocked && preauthorized) {
405
- if (await this.getCommands().preauthorize(false)) {
406
- return;
407
- }
408
- }
409
- const expectedState = this.getState()?.staticSessionId;
410
- const { message } = await this.getCurrentSession().typedCall('GetAddress', 'Address', {
411
- address_n: [(0, pathUtils_1.toHardened)(44), (0, pathUtils_1.toHardened)(1), (0, pathUtils_1.toHardened)(0), 0, 0],
412
- coin_name: 'Testnet',
413
- script_type: 'SPENDADDRESS',
414
- });
415
- const uniqueState = `${message.address}@${this.features.device_id}:${this.instance}`;
416
- if (this.features.session_id) {
417
- this.setState({ sessionId: this.features.session_id });
418
- }
419
- if (expectedState && expectedState !== uniqueState) {
420
- return uniqueState;
421
- }
422
- if (!expectedState) {
423
- this.setState({ staticSessionId: uniqueState });
424
- }
425
- }
426
385
  async initialize(useCardanoDerivation) {
427
386
  let payload;
428
387
  if (this.features) {
@@ -570,12 +529,12 @@ class Device extends utils_1.TypedEmitter {
570
529
  return response.message;
571
530
  }
572
531
  const length = payload.byteLength;
573
- let response = await this.getCurrentSession().typedCall('ChangeLanguage', ['TranslationDataRequest', 'Success'], { data_length: length });
532
+ let response = await this.getCurrentSession().typedCall('ChangeLanguage', ['DataChunkRequest', 'Success'], { data_length: length });
574
533
  while (response.type !== 'Success') {
575
534
  const start = response.message.data_offset;
576
535
  const end = response.message.data_offset + response.message.data_length;
577
536
  const chunk = payload.slice(start, end);
578
- response = await this.getCurrentSession().typedCall('TranslationDataAck', ['TranslationDataRequest', 'Success'], {
537
+ response = await this.getCurrentSession().typedCall('DataChunkAck', ['DataChunkRequest', 'Success'], {
579
538
  data_chunk: Buffer.from(chunk).toString('hex'),
580
539
  });
581
540
  }
@@ -616,7 +575,6 @@ class Device extends utils_1.TypedEmitter {
616
575
  this.availableTranslations = this.firmwareRelease?.translations ?? [];
617
576
  }
618
577
  this._features = feat;
619
- this._featuresNeedsReload = false;
620
578
  if (feat.fw_vendor === 'Trezor Bitcoin-only') {
621
579
  this._firmwareType = types_1.FirmwareType.BitcoinOnly;
622
580
  }
@@ -631,7 +589,7 @@ class Device extends utils_1.TypedEmitter {
631
589
  ? types_1.FirmwareType.BitcoinOnly
632
590
  : types_1.FirmwareType.Regular;
633
591
  }
634
- const deviceInfo = models_1.models[feat.internal_model] ?? {
592
+ const deviceInfo = device_utils_1.models[feat.internal_model] ?? {
635
593
  name: `Unknown ${feat.internal_model}`,
636
594
  colors: {},
637
595
  };
@@ -665,8 +623,14 @@ class Device extends utils_1.TypedEmitter {
665
623
  }
666
624
  disconnect() {
667
625
  _log.debug('Disconnect cleanup');
626
+ this.transport.off(transport_1.TRANSPORT.STOPPED, this.onTransportStopped);
627
+ this.transport.deviceEvents.off(this.transportPath, this.onTransportDeviceEvent);
628
+ this.removeAllListeners();
668
629
  this.sessionDfd?.reject(new Error());
669
- this.lastAcquiredHere = false;
630
+ if (this.sessionAcquired) {
631
+ this.transport.releaseSync(this.sessionAcquired);
632
+ this.sessionAcquired = null;
633
+ }
670
634
  this.emitLifecycle(events_1.DEVICE.DISCONNECT);
671
635
  this.emitLifecycle = () => { };
672
636
  return this.interrupt(constants_1.ERRORS.TypedError('Device_Disconnected'));
@@ -680,9 +644,6 @@ class Device extends utils_1.TypedEmitter {
680
644
  isSeedless() {
681
645
  return this.features && !!this.features.no_backup;
682
646
  }
683
- isInconsistent() {
684
- return this.inconsistent;
685
- }
686
647
  getVersion() {
687
648
  if (!this.features)
688
649
  return;
@@ -700,13 +661,13 @@ class Device extends utils_1.TypedEmitter {
700
661
  return utils_1.versionUtils.isNewerOrEqual(version, modelVersion);
701
662
  }
702
663
  isUsed() {
703
- return typeof this.session === 'string';
664
+ return !!this.transport.getDescriptor(this.transportPath)?.session;
704
665
  }
705
666
  isUsedHere() {
706
- return this.isUsed() && this.lastAcquiredHere;
667
+ return !!this.sessionAcquired;
707
668
  }
708
669
  isUsedElsewhere() {
709
- return this.isUsed() && !this.lastAcquiredHere;
670
+ return this.isUsed() && !this.isUsedHere();
710
671
  }
711
672
  getUniquePath() {
712
673
  return this.uniquePath;
@@ -731,12 +692,6 @@ class Device extends utils_1.TypedEmitter {
731
692
  }
732
693
  return null;
733
694
  }
734
- dispose() {
735
- this.removeAllListeners();
736
- if (this.session && this.lastAcquiredHere) {
737
- this.transport.releaseSync(this.session);
738
- }
739
- }
740
695
  getMode() {
741
696
  if (this.features.bootloader_mode)
742
697
  return 'bootloader';
@@ -759,23 +714,22 @@ class Device extends utils_1.TypedEmitter {
759
714
  };
760
715
  }
761
716
  if (this.isUnacquired()) {
717
+ const sessionOwner = this.transport.getDescriptor(this.transportPath)?.sessionOwner;
762
718
  return {
763
719
  ...base,
764
720
  type: 'unacquired',
765
721
  label: 'Unacquired device',
766
722
  name: this.name,
767
- transportSessionOwner: this.lastAcquiredHere
768
- ? undefined
769
- : this.transportSessionOwner,
723
+ transportSessionOwner: this.sessionAcquired ? undefined : sessionOwner,
770
724
  bluetoothProps: this.bluetoothProps,
771
725
  thp: this.thp?.serialize(),
772
726
  };
773
727
  }
774
728
  const defaultLabel = 'My Trezor';
775
729
  const label = this.features.label === '' || !this.features.label ? defaultLabel : this.features.label;
776
- let status = this.isUsedElsewhere() ? 'occupied' : 'available';
777
- if (this._featuresNeedsReload)
778
- status = 'used';
730
+ const status = this.isUsedElsewhere()
731
+ ? 'occupied'
732
+ : (this.wasUsedElsewhere && 'used') || 'available';
779
733
  return {
780
734
  ...base,
781
735
  type: 'acquired',