@tronweb3/tronwallet-adapter-trust 1.0.3 → 1.2.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/src/adapter.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import {
2
- Adapter,
3
2
  AdapterState,
4
3
  isInBrowser,
5
4
  WalletReadyState,
@@ -9,6 +8,9 @@ import {
9
8
  WalletConnectionError,
10
9
  WalletSignTransactionError,
11
10
  WalletGetNetworkError,
11
+ AddonAdapter,
12
+ WalletError,
13
+ assertConnectAddress,
12
14
  } from '@tronweb3/tronwallet-abstract-adapter';
13
15
  import type {
14
16
  Transaction,
@@ -33,23 +35,11 @@ declare global {
33
35
  }
34
36
  }
35
37
 
36
- export interface TrustAdapterConfig extends BaseAdapterConfig {
37
- /**
38
- * Timeout in millisecond for checking if Trust wallet exists.
39
- * Default is 2 * 1000ms
40
- */
41
- checkTimeout?: number;
42
-
43
- /**
44
- * Set if open app using DeepLink.
45
- * Default is true.
46
- */
47
- openAppWithDeeplink?: boolean;
48
- }
38
+ export type TrustAdapterConfig = BaseAdapterConfig;
49
39
 
50
40
  export const TrustAdapterName = 'Trust' as AdapterName<'Trust'>;
51
41
 
52
- export class TrustAdapter extends Adapter {
42
+ export class TrustAdapter extends AddonAdapter {
53
43
  name = TrustAdapterName;
54
44
  url = 'https://trustwallet.com';
55
45
  icon =
@@ -64,17 +54,10 @@ export class TrustAdapter extends Adapter {
64
54
  private _address: string | null;
65
55
 
66
56
  constructor(config: TrustAdapterConfig = {}) {
67
- super();
68
- const { checkTimeout = 2 * 1000, openUrlWhenWalletNotFound = true, openAppWithDeeplink = true } = config;
69
-
70
- if (typeof checkTimeout !== 'number') {
71
- throw new Error('[TrustAdapter] config.checkTimeout should be a number');
72
- }
57
+ super(config);
73
58
 
74
59
  this.config = {
75
- checkTimeout,
76
- openAppWithDeeplink,
77
- openUrlWhenWalletNotFound,
60
+ ...this.commonConfig,
78
61
  };
79
62
  this._connecting = false;
80
63
  this._wallet = null;
@@ -87,7 +70,11 @@ export class TrustAdapter extends Adapter {
87
70
  }
88
71
  if (supportTrust()) {
89
72
  this._readyState = WalletReadyState.Found;
90
- this._updateWallet();
73
+ this._updateWallet().then(() => {
74
+ if (this.connected) {
75
+ this.emit('connect', this.address || '');
76
+ }
77
+ });
91
78
  } else {
92
79
  this._checkWallet().then(() => {
93
80
  if (this.connected) {
@@ -134,45 +121,34 @@ export class TrustAdapter extends Adapter {
134
121
  }
135
122
  }
136
123
 
137
- async connect(): Promise<void> {
124
+ protected async _connect(): Promise<void> {
138
125
  try {
139
- this.checkIfopenTrustWallet();
140
- if (this.connected || this.connecting) return;
141
- await this._checkWallet();
142
- if (this.state === AdapterState.NotFound) {
143
- if (this.config.openUrlWhenWalletNotFound !== false && isInBrowser()) {
144
- window.open(this.url, '_blank');
145
- }
146
- throw new WalletNotFoundError();
147
- }
126
+ if (!(await this._beforeConnect())) return;
148
127
  if (!this._wallet) return;
149
128
  this._connecting = true;
150
129
  const wallet = this._wallet as TronLinkWallet;
151
- try {
152
- const res = await wallet.request({ method: 'tron_requestAccounts' });
153
- if (!res) {
154
- throw new WalletConnectionError('Request connect error.');
155
- }
156
- if (res.code === 4000) {
157
- throw new WalletConnectionError(
158
- 'The same DApp has already initiated a request to connect to trustwallet, and the pop-up window has not been closed.'
159
- );
160
- }
161
- if (res.code === 4001) {
162
- throw new WalletConnectionError('The user rejected connection.');
163
- }
164
- } catch (error: any) {
165
- throw new WalletConnectionError(error?.message, error);
130
+ const res = await wallet.request({ method: 'tron_requestAccounts' });
131
+ if (!res) {
132
+ throw new WalletConnectionError('Request connect error.');
133
+ }
134
+ if (res.code === 4000) {
135
+ throw new WalletConnectionError(
136
+ 'The same DApp has already initiated a request to connect to trustwallet, and the pop-up window has not been closed.'
137
+ );
138
+ }
139
+ if (res.code === 4001) {
140
+ throw new WalletConnectionError('The user rejected connection.');
166
141
  }
167
142
 
168
- const address = wallet.tronWeb.defaultAddress?.base58 || '';
143
+ const address = assertConnectAddress(wallet.tronWeb.defaultAddress?.base58);
169
144
  this.setAddress(address);
170
145
  this.setState(AdapterState.Connected);
171
146
  this._listenEvent();
172
147
  this.connected && this.emit('connect', this.address || '');
173
148
  } catch (error: any) {
174
- this.emit('error', error);
175
- throw error;
149
+ const err = error instanceof WalletError ? error : new WalletConnectionError(error?.message, error);
150
+ this.emit('error', err);
151
+ throw err;
176
152
  } finally {
177
153
  this._connecting = false;
178
154
  }
@@ -265,20 +241,59 @@ export class TrustAdapter extends Adapter {
265
241
  }
266
242
 
267
243
  private _stopListenEvent() {
244
+ this._eventGeneration++;
245
+ if (this._accountsChangedTimer) {
246
+ clearTimeout(this._accountsChangedTimer);
247
+ this._accountsChangedTimer = null;
248
+ }
268
249
  window.removeEventListener('message', this.messageHandler);
269
250
  }
270
251
 
271
- private messageHandler = (e: TronLinkMessageEvent) => {
252
+ private _accountsChangedTimer: ReturnType<typeof setTimeout> | null = null;
253
+ /**
254
+ * Incremented whenever the listening session ends. Deferred `accountsChanged`
255
+ * work and the awaited `connect` handler capture the value before they yield and
256
+ * abandon themselves if it no longer matches, so an event that started before
257
+ * `disconnect()` cannot write state after it.
258
+ */
259
+ private _eventGeneration = 0;
260
+
261
+ private messageHandler = async (e: TronLinkMessageEvent) => {
262
+ if (e.origin !== window.location.origin) {
263
+ return;
264
+ }
265
+
272
266
  const message = e.data?.message;
273
267
  if (!message) {
274
268
  return;
275
269
  }
276
270
  if (message.action === 'accountsChanged') {
277
- setTimeout(() => {
271
+ if (this._accountsChangedTimer) {
272
+ clearTimeout(this._accountsChangedTimer);
273
+ }
274
+ const generation = this._eventGeneration;
275
+ this._accountsChangedTimer = setTimeout(async () => {
276
+ this._accountsChangedTimer = null;
277
+ if (generation !== this._eventGeneration) return;
278
278
  const preAddr = this.address || '';
279
- if ((this._wallet as TronLinkWallet)?.ready) {
280
- const address = (message.data as AccountsChangedEventData).address;
281
- this.setAddress(address);
279
+ const curAddr = (message.data as AccountsChangedEventData).address || '';
280
+ // An empty account is a disconnection, not a connection to the empty
281
+ // address. Going Connected here leaves `connected === true` with no
282
+ // address, and the block below then emits `disconnect` against a state
283
+ // that still says Connected.
284
+ if (curAddr && (this._wallet as TronLinkWallet)?.ready) {
285
+ try {
286
+ await this.checkSecurity();
287
+ } catch {
288
+ if (generation !== this._eventGeneration) return;
289
+ this.setAddress(null);
290
+ this.setState(AdapterState.Disconnect);
291
+ return;
292
+ }
293
+ // `checkSecurity()` was awaited, so the session may have ended while it
294
+ // was pending — re-check before writing any state.
295
+ if (generation !== this._eventGeneration) return;
296
+ this.setAddress(curAddr);
282
297
  this.setState(AdapterState.Connected);
283
298
  } else {
284
299
  this.setAddress(null);
@@ -295,9 +310,31 @@ export class TrustAdapter extends Adapter {
295
310
  }
296
311
  }, 200);
297
312
  } else if (message.action === 'connect') {
313
+ const generation = this._eventGeneration;
314
+ // Trust may post a `connect` message before the address is available;
315
+ // ignore it so we don't report a connection (and emit connect) with no address.
316
+ if (!(this._wallet as TronLinkWallet).tronWeb?.defaultAddress?.base58) {
317
+ return;
318
+ }
319
+ try {
320
+ await this.checkSecurity();
321
+ } catch {
322
+ // A stale rejection must not tear down a session that has since been
323
+ // re-established, so the generation is honoured on this path too.
324
+ if (generation !== this._eventGeneration) return;
325
+ this.setAddress(null);
326
+ this.setState(AdapterState.Disconnect);
327
+ return;
328
+ }
329
+ // `checkSecurity()` was awaited, so the session may have ended while it
330
+ // was pending — re-check before writing any state.
331
+ if (generation !== this._eventGeneration) return;
332
+ // Re-read after the await: the account may have changed or been cleared
333
+ // while the check was pending.
334
+ const address = (this._wallet as TronLinkWallet).tronWeb?.defaultAddress?.base58 || '';
335
+ if (!address) return;
298
336
  const isCurConnected = this.connected;
299
337
  const preAddress = this.address || '';
300
- const address = (this._wallet as TronLinkWallet).tronWeb?.defaultAddress?.base58 || '';
301
338
  this.setAddress(address);
302
339
  this.setState(AdapterState.Connected);
303
340
  if (!isCurConnected) {
@@ -321,12 +358,24 @@ export class TrustAdapter extends Adapter {
321
358
  }
322
359
  }
323
360
 
361
+ protected _openAppByDeepLinkIfNeed(): boolean {
362
+ if (this.config.openAppWithDeeplink === false) {
363
+ return false;
364
+ }
365
+ return openTrustWallet();
366
+ }
367
+
324
368
  private _checkPromise: Promise<boolean> | null = null;
369
+ /**
370
+ * Detection polls for the full `checkTimeout` only once. Later attempts re-check a
371
+ * single time, so retrying is free when the wallet is genuinely absent.
372
+ */
373
+ private _hasRunInitialDetection = false;
325
374
  /**
326
375
  * check if wallet exists by interval, the promise only resolve when wallet detected or timeout
327
376
  * @returns if trustwallet exists
328
377
  */
329
- private _checkWallet(): Promise<boolean> {
378
+ protected _checkWallet(): Promise<boolean> {
330
379
  if (this.readyState === WalletReadyState.Found) {
331
380
  return Promise.resolve(true);
332
381
  }
@@ -334,10 +383,11 @@ export class TrustAdapter extends Adapter {
334
383
  return this._checkPromise;
335
384
  }
336
385
  const interval = 100;
337
- const maxTimes = Math.floor(this.config.checkTimeout / interval);
386
+ const maxTimes = this._hasRunInitialDetection ? 0 : Math.floor(this.config.checkTimeout / interval);
387
+ this._hasRunInitialDetection = true;
338
388
  let times = 0,
339
389
  timer: ReturnType<typeof setInterval>;
340
- this._checkPromise = new Promise((resolve) => {
390
+ const detection = new Promise<boolean>((resolve) => {
341
391
  const check = () => {
342
392
  times++;
343
393
  const isSupport = supportTrust();
@@ -352,17 +402,38 @@ export class TrustAdapter extends Adapter {
352
402
  timer = setInterval(check, interval);
353
403
  check();
354
404
  });
355
- return this._checkPromise;
405
+ this._checkPromise = detection;
406
+ // Never cache a failed detection. The extension may inject late, be switched on at
407
+ // runtime, or a mobile WebView may still be initialising — in all of those cases the
408
+ // next call has to look again instead of replaying the old negative answer.
409
+ void detection.then((found) => {
410
+ if (!found && this._checkPromise === detection) {
411
+ this._checkPromise = null;
412
+ }
413
+ });
414
+ return detection;
356
415
  }
357
416
 
358
- private _updateWallet = () => {
359
- let state = this.state;
360
- let address = this.address;
417
+ private _updateWallet = async () => {
418
+ let state;
419
+ let address;
361
420
  if (supportTrust()) {
362
421
  this._wallet = window.trustwallet!.tronLink;
363
422
  this._listenEvent();
364
423
  address = this._wallet.tronWeb?.defaultAddress?.base58 || null;
365
- state = this._wallet.ready ? AdapterState.Connected : AdapterState.Disconnect;
424
+ if (address) {
425
+ // Only run the security check once the wallet is actually connected.
426
+ try {
427
+ await this.checkSecurity();
428
+ } catch {
429
+ this.setAddress(null);
430
+ this.setState(AdapterState.Disconnect);
431
+ return;
432
+ }
433
+ state = AdapterState.Connected;
434
+ } else {
435
+ state = AdapterState.Disconnect;
436
+ }
366
437
  } else {
367
438
  this._wallet = null;
368
439
  address = null;