tiwiflix-wallet-connector 1.5.4 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -2508,9 +2508,37 @@ class WalletConnector {
2508
2508
  });
2509
2509
  this.eventEmitter.emit(WalletEvent.CONNECTED, account);
2510
2510
  }
2511
+ else {
2512
+ // No account found - user likely cancelled or connection failed
2513
+ this.debugTools.info('WALLET_CONNECTOR', 'No account found after visibility change - resetting state');
2514
+ clearTimeout(connectionTimeout);
2515
+ clearTimeout(safetyTimeout);
2516
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
2517
+ this.updateState({
2518
+ status: ConnectionStatus.DISCONNECTED,
2519
+ wallet: null,
2520
+ error: new Error('Connection cancelled or failed'),
2521
+ });
2522
+ this.eventEmitter.emit(WalletEvent.ERROR, new Error('Connection cancelled or failed'));
2523
+ }
2511
2524
  }
2512
2525
  catch (err) {
2513
2526
  this.debugTools.warn('WALLET_CONNECTOR', 'Failed to check account on visibility change', err);
2527
+ // On error checking account, reset state after a longer delay
2528
+ setTimeout(() => {
2529
+ if (this.state.status === ConnectionStatus.CONNECTING) {
2530
+ this.debugTools.info('WALLET_CONNECTOR', 'Still connecting after visibility check error - resetting state');
2531
+ clearTimeout(connectionTimeout);
2532
+ clearTimeout(safetyTimeout);
2533
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
2534
+ this.updateState({
2535
+ status: ConnectionStatus.DISCONNECTED,
2536
+ wallet: null,
2537
+ error: new Error('Connection timeout'),
2538
+ });
2539
+ this.eventEmitter.emit(WalletEvent.ERROR, new Error('Connection timeout'));
2540
+ }
2541
+ }, 3000); // Give it 3 more seconds
2514
2542
  }
2515
2543
  }
2516
2544
  }, 1000);
@@ -7502,7 +7530,7 @@ const defaultStyles = {
7502
7530
  fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
7503
7531
  boxShadow: '0 4px 14px 0 rgba(51, 150, 255, 0.39)',
7504
7532
  }};
7505
- const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet', fetchTransactions, getExplorerUrl, fetchBalance: fetchBalanceProp, }) => {
7533
+ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet Now', fetchTransactions, getExplorerUrl, fetchBalance: fetchBalanceProp, }) => {
7506
7534
  // Provide a default fetchBalance if not passed
7507
7535
  const fetchBalance = fetchBalanceProp || (async (account) => {
7508
7536
  // Default: return zero balance with symbol based on chain
@@ -7634,12 +7662,15 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7634
7662
  });
7635
7663
  // Stable USD value updater - only updates if value changes significantly
7636
7664
  const setUsdValueStable = useCallback((newValue) => {
7637
- // Set USD value immediately - only skip if value is the same
7638
7665
  setUsdValue((prevValue) => {
7639
7666
  // If new value is null/undefined and we have a previous value, keep it
7640
7667
  if ((newValue === null || newValue === undefined) && prevValue !== null) {
7641
7668
  return prevValue;
7642
7669
  }
7670
+ // Only update if value actually changed
7671
+ if (prevValue === newValue) {
7672
+ return prevValue;
7673
+ }
7643
7674
  // Otherwise set the new value
7644
7675
  return newValue;
7645
7676
  });
@@ -7853,9 +7884,12 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7853
7884
  const walletsRef = useRef(null);
7854
7885
  // Initialize and subscribe to events
7855
7886
  useEffect(() => {
7887
+ let isMounted = true;
7856
7888
  // Get initial state
7857
7889
  const initialState = connector.getState();
7858
7890
  setStatus(initialState.status);
7891
+ // Safety: Clear isConnecting on mount (in case it was stuck from previous session)
7892
+ setIsConnecting(false);
7859
7893
  // Only set wallets once to prevent re-renders
7860
7894
  if (!walletsRef.current) {
7861
7895
  walletsRef.current = connector.getAllWallets();
@@ -7872,6 +7906,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7872
7906
  setBalanceLoading(false);
7873
7907
  }, 2000);
7874
7908
  connector.getAccount().then(async (acc) => {
7909
+ if (!isMounted)
7910
+ return;
7875
7911
  setAccount(acc);
7876
7912
  // Load balance when account is available
7877
7913
  if (acc && acc.chainType === 'ton') {
@@ -7882,10 +7918,14 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7882
7918
  fetchTONBalance(acc),
7883
7919
  fetchTONNFTs(acc)
7884
7920
  ]);
7921
+ if (!isMounted)
7922
+ return;
7885
7923
  clearTimeout(safetyTimeout);
7886
7924
  setIsInitializing(false);
7887
7925
  }
7888
7926
  catch (err) {
7927
+ if (!isMounted)
7928
+ return;
7889
7929
  clearTimeout(safetyTimeout);
7890
7930
  setIsInitializing(false);
7891
7931
  }
@@ -7894,6 +7934,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7894
7934
  try {
7895
7935
  setBalanceLoading(true);
7896
7936
  const hasRealBalance = await loadBalance();
7937
+ if (!isMounted)
7938
+ return;
7897
7939
  // Always stop loading state after balance fetch
7898
7940
  setBalanceLoading(false);
7899
7941
  clearTimeout(safetyTimeout);
@@ -7901,6 +7943,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7901
7943
  setIsInitializing(false);
7902
7944
  }
7903
7945
  catch (err) {
7946
+ if (!isMounted)
7947
+ return;
7904
7948
  setBalanceLoading(false);
7905
7949
  clearTimeout(safetyTimeout);
7906
7950
  // Always stop initializing on error
@@ -7912,6 +7956,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7912
7956
  const state = connector.getState();
7913
7957
  const stateTwcBalance = state.twcBalance;
7914
7958
  if (acc && stateTwcBalance && stateTwcBalance !== '0' && stateTwcBalance !== '0.00') {
7959
+ if (!isMounted)
7960
+ return;
7915
7961
  setTwcBalance(stateTwcBalance);
7916
7962
  setUsdValueStable(state.usdValue ?? null);
7917
7963
  // Save to cache
@@ -7925,6 +7971,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7925
7971
  // Try to load from cache as fallback
7926
7972
  const cached = loadTWCBalanceFromCache(acc.address);
7927
7973
  if (cached?.balance && cached.balance !== '0' && cached.balance !== '0.00') {
7974
+ if (!isMounted)
7975
+ return;
7928
7976
  setTwcBalance(cached.balance);
7929
7977
  setUsdValueStable(cached.usdValue);
7930
7978
  clearTimeout(safetyTimeout);
@@ -7934,6 +7982,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7934
7982
  // No real balance yet, but for EVM wallets, proactively fetch with retries
7935
7983
  if (acc.chainType === 'evm') {
7936
7984
  fetchTWCBalanceWithRetry(2, 200).then((result) => {
7985
+ if (!isMounted)
7986
+ return;
7937
7987
  if (result) {
7938
7988
  setTwcBalance(result.balance);
7939
7989
  setUsdValueStable(result.usdValue);
@@ -7950,6 +8000,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7950
8000
  setIsInitializing(false);
7951
8001
  }
7952
8002
  }).catch((error) => {
8003
+ if (!isMounted)
8004
+ return;
7953
8005
  // On error - show connected button with 0 balance
7954
8006
  setTwcBalance('0');
7955
8007
  clearTimeout(safetyTimeout);
@@ -7965,9 +8017,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7965
8017
  fetchTONBalance(acc),
7966
8018
  fetchTONNFTs(acc)
7967
8019
  ]).then(() => {
8020
+ if (!isMounted)
8021
+ return;
7968
8022
  clearTimeout(safetyTimeout);
7969
8023
  setIsInitializing(false);
7970
8024
  }).catch((err) => {
8025
+ if (!isMounted)
8026
+ return;
7971
8027
  clearTimeout(safetyTimeout);
7972
8028
  setIsInitializing(false);
7973
8029
  });
@@ -7975,6 +8031,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7975
8031
  else if (fetchBalance) {
7976
8032
  // For other non-EVM chains: If fetchBalance is provided, load balance and then stop initializing
7977
8033
  fetchTWCBalanceWithRetry(1, 100).then((result) => {
8034
+ if (!isMounted)
8035
+ return;
7978
8036
  if (result) {
7979
8037
  setTwcBalance(result.balance);
7980
8038
  setUsdValueStable(result.usdValue);
@@ -7986,12 +8044,16 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7986
8044
  clearTimeout(safetyTimeout);
7987
8045
  setIsInitializing(false);
7988
8046
  }).catch(() => {
8047
+ if (!isMounted)
8048
+ return;
7989
8049
  // Stop initializing even on error
7990
8050
  clearTimeout(safetyTimeout);
7991
8051
  setIsInitializing(false);
7992
8052
  });
7993
8053
  }
7994
8054
  else {
8055
+ if (!isMounted)
8056
+ return;
7995
8057
  // No fetchBalance and no cached balance - stop initializing with 0 balance
7996
8058
  setTwcBalance('0');
7997
8059
  clearTimeout(safetyTimeout);
@@ -8002,6 +8064,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8002
8064
  }
8003
8065
  }
8004
8066
  }).catch((err) => {
8067
+ if (!isMounted)
8068
+ return;
8005
8069
  clearTimeout(safetyTimeout);
8006
8070
  setIsInitializing(false);
8007
8071
  // If we can't get account even though status is connected, disconnect
@@ -8014,9 +8078,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8014
8078
  setIsInitializing(true);
8015
8079
  // Set a shorter timeout to prevent stuck loading state
8016
8080
  initTimeout = setTimeout(() => {
8081
+ if (!isMounted)
8082
+ return;
8017
8083
  setIsInitializing(false);
8018
8084
  // If still no account after timeout, ensure we show connect button
8019
8085
  connector.getAccount().then((acc) => {
8086
+ if (!isMounted)
8087
+ return;
8020
8088
  if (!acc) {
8021
8089
  setAccount(null);
8022
8090
  // If status is disconnected and no account, ensure we're fully disconnected
@@ -8028,11 +8096,15 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8028
8096
  }
8029
8097
  }
8030
8098
  }).catch((err) => {
8099
+ if (!isMounted)
8100
+ return;
8031
8101
  setAccount(null);
8032
8102
  setStatus(ConnectionStatus.DISCONNECTED);
8033
8103
  });
8034
8104
  }, 1500); // Reduced to 1.5 second timeout to prevent stuck loading
8035
8105
  connector.getAccount().then((acc) => {
8106
+ if (!isMounted)
8107
+ return;
8036
8108
  if (initTimeout)
8037
8109
  clearTimeout(initTimeout);
8038
8110
  setAccount(acc);
@@ -8047,6 +8119,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8047
8119
  }
8048
8120
  }
8049
8121
  }).catch((err) => {
8122
+ if (!isMounted)
8123
+ return;
8050
8124
  if (initTimeout)
8051
8125
  clearTimeout(initTimeout);
8052
8126
  setIsInitializing(false);
@@ -8060,7 +8134,11 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8060
8134
  }
8061
8135
  // Subscribe to events
8062
8136
  const unsubscribeAccount = connector.on(WalletEvent.ACCOUNT_CHANGED, async (acc) => {
8137
+ if (!isMounted)
8138
+ return;
8063
8139
  setAccount(acc);
8140
+ // Clear isConnecting when account changes (connection succeeded)
8141
+ setIsConnecting(false);
8064
8142
  // Don't set isInitializing to false yet - wait for balance to load
8065
8143
  // Load cached balance for new account immediately
8066
8144
  if (acc?.address) {
@@ -8078,9 +8156,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8078
8156
  fetchTONBalance(acc),
8079
8157
  fetchTONNFTs(acc)
8080
8158
  ]);
8159
+ if (!isMounted)
8160
+ return;
8081
8161
  setIsInitializing(false);
8082
8162
  }
8083
8163
  catch (err) {
8164
+ if (!isMounted)
8165
+ return;
8084
8166
  setIsInitializing(false);
8085
8167
  }
8086
8168
  }
@@ -8088,9 +8170,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8088
8170
  setBalanceLoading(true);
8089
8171
  try {
8090
8172
  await loadBalance();
8173
+ if (!isMounted)
8174
+ return;
8091
8175
  setIsInitializing(false);
8092
8176
  }
8093
8177
  catch (err) {
8178
+ if (!isMounted)
8179
+ return;
8094
8180
  setIsInitializing(false);
8095
8181
  setBalanceLoading(false);
8096
8182
  }
@@ -8125,9 +8211,14 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8125
8211
  });
8126
8212
  const unsubscribeStatus = connector.on(WalletEvent.STATUS_CHANGED, (newStatus) => {
8127
8213
  setStatus(newStatus);
8128
- // If status changes to disconnected, stop initializing
8214
+ // If status changes to disconnected, stop initializing and connecting
8129
8215
  if (newStatus === ConnectionStatus.DISCONNECTED) {
8130
8216
  setIsInitializing(false);
8217
+ setIsConnecting(false);
8218
+ }
8219
+ // If status changes to connected, clear connecting state
8220
+ if (newStatus === ConnectionStatus.CONNECTED) {
8221
+ setIsConnecting(false);
8131
8222
  }
8132
8223
  });
8133
8224
  const unsubscribeDisconnect = connector.on(WalletEvent.DISCONNECTED, () => {
@@ -8208,6 +8299,7 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8208
8299
  }
8209
8300
  });
8210
8301
  return () => {
8302
+ isMounted = false;
8211
8303
  clearTimeout(initTimeout);
8212
8304
  unsubscribeAccount();
8213
8305
  unsubscribeStatus();
@@ -8353,16 +8445,23 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8353
8445
  }, [connector]);
8354
8446
  // Load balance when account changes
8355
8447
  useEffect(() => {
8448
+ let isActive = true;
8356
8449
  if (account && fetchBalance) {
8357
8450
  setBalanceLoading(true);
8358
8451
  loadBalance()
8359
8452
  .then(() => {
8453
+ if (!isActive)
8454
+ return;
8360
8455
  // Always stop initializing once balance request completes
8361
8456
  })
8362
8457
  .catch(() => {
8458
+ if (!isActive)
8459
+ return;
8363
8460
  // Ignore errors, we still clear loading states
8364
8461
  })
8365
8462
  .finally(() => {
8463
+ if (!isActive)
8464
+ return;
8366
8465
  setBalanceLoading(false);
8367
8466
  setIsInitializing(false);
8368
8467
  });
@@ -8395,6 +8494,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8395
8494
  // Only fetch if we don't have a balance yet
8396
8495
  if (!hasBalance) {
8397
8496
  fetchTWCBalanceWithRetry(2, 200).then((result) => {
8497
+ if (!isActive)
8498
+ return;
8398
8499
  if (result) {
8399
8500
  setTwcBalance(result.balance);
8400
8501
  setUsdValueStable(result.usdValue);
@@ -8410,6 +8511,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8410
8511
  setIsInitializing(false);
8411
8512
  }
8412
8513
  }).catch(() => {
8514
+ if (!isActive)
8515
+ return;
8413
8516
  setIsInitializing(false);
8414
8517
  });
8415
8518
  }
@@ -8430,22 +8533,33 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8430
8533
  }
8431
8534
  }
8432
8535
  }
8536
+ return () => {
8537
+ isActive = false;
8538
+ };
8433
8539
  // eslint-disable-next-line react-hooks/exhaustive-deps
8434
8540
  }, [account?.address, fetchBalance]);
8435
8541
  // Polling mechanism as fallback to ensure TWC balance is always fetched
8436
8542
  useEffect(() => {
8437
- if (!account || account.chainType !== 'evm' || isInitializing) {
8543
+ if (!account || account.chainType !== 'evm') {
8438
8544
  return;
8439
8545
  }
8440
- // If we don't have a balance yet, start polling
8441
- const hasBalance = twcBalance && twcBalance !== '0' && twcBalance !== '0.00';
8442
- if (!hasBalance) {
8546
+ // Check if we already have a balance
8547
+ const currentBalance = twcBalance;
8548
+ const hasBalance = currentBalance && currentBalance !== '0' && currentBalance !== '0.00';
8549
+ if (!hasBalance && !isInitializing) {
8443
8550
  // Poll every 2 seconds, up to 3 times (6 seconds total) - faster polling
8444
8551
  let pollCount = 0;
8445
8552
  const maxPolls = 3;
8553
+ let isActive = true;
8446
8554
  const pollInterval = setInterval(() => {
8555
+ if (!isActive) {
8556
+ clearInterval(pollInterval);
8557
+ return;
8558
+ }
8447
8559
  pollCount++;
8448
8560
  fetchTWCBalanceWithRetry(2, 200).then((result) => {
8561
+ if (!isActive)
8562
+ return;
8449
8563
  if (result) {
8450
8564
  setTwcBalance(result.balance);
8451
8565
  setUsdValueStable(result.usdValue);
@@ -8458,17 +8572,22 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8458
8572
  clearInterval(pollInterval);
8459
8573
  }
8460
8574
  }).catch((error) => {
8575
+ if (!isActive)
8576
+ return;
8461
8577
  if (pollCount >= maxPolls) {
8462
8578
  clearInterval(pollInterval);
8463
8579
  }
8464
8580
  });
8465
8581
  }, 2000); // Poll every 2 seconds (faster)
8466
8582
  return () => {
8583
+ isActive = false;
8467
8584
  clearInterval(pollInterval);
8468
8585
  };
8469
8586
  }
8587
+ return undefined;
8588
+ // Only depend on account address - check balance inside effect
8470
8589
  // eslint-disable-next-line react-hooks/exhaustive-deps
8471
- }, [account?.address, twcBalance, isInitializing]);
8590
+ }, [account?.address]);
8472
8591
  // Load transactions when details modal opens
8473
8592
  useEffect(() => {
8474
8593
  if (showDetailsModal && account) {
@@ -8478,6 +8597,11 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8478
8597
  const handleConnect = useCallback(async (walletType) => {
8479
8598
  setIsConnecting(true);
8480
8599
  setError(null);
8600
+ // Safety timeout: ensure isConnecting is cleared after 75 seconds maximum
8601
+ const safetyTimeout = setTimeout(() => {
8602
+ setIsConnecting(false);
8603
+ setIsInitializing(false);
8604
+ }, 75000);
8481
8605
  // Close modal BEFORE connecting for TON wallets and WalletConnect
8482
8606
  // so external modals can show
8483
8607
  const isTonWallet = ['tonkeeper', 'tonhub', 'mytonwallet'].includes(walletType);
@@ -8491,27 +8615,20 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8491
8615
  setShowSendModal(false);
8492
8616
  }
8493
8617
  try {
8494
- const result = await connector.connect(walletType);
8495
- // Set account from connection result immediately
8496
- if (result?.account) {
8497
- setAccount(result.account);
8498
- setIsInitializing(false);
8499
- }
8500
- else {
8501
- // Fallback: get account after connection
8502
- const newAccount = await connector.getAccount();
8503
- if (newAccount) {
8504
- setAccount(newAccount);
8505
- setIsInitializing(false);
8506
- }
8507
- }
8618
+ // connector.connect() returns void - state updates happen via events
8619
+ await connector.connect(walletType);
8620
+ // Connection succeeded - state is updated via ACCOUNT_CHANGED event
8621
+ // which also clears isConnecting
8508
8622
  // Close modal after connection for non-TON/walletconnect wallets
8509
8623
  if (!isTonWallet && !isWalletConnect) {
8510
8624
  setShowModal(false);
8511
8625
  }
8512
- setIsConnecting(false);
8626
+ // Clear safety timeout on success
8627
+ clearTimeout(safetyTimeout);
8513
8628
  }
8514
8629
  catch (err) {
8630
+ // Clear safety timeout on error
8631
+ clearTimeout(safetyTimeout);
8515
8632
  // Always clear connection state on error
8516
8633
  setAccount(null);
8517
8634
  setStatus(ConnectionStatus.DISCONNECTED);
package/dist/index.js CHANGED
@@ -2510,9 +2510,37 @@ class WalletConnector {
2510
2510
  });
2511
2511
  this.eventEmitter.emit(exports.WalletEvent.CONNECTED, account);
2512
2512
  }
2513
+ else {
2514
+ // No account found - user likely cancelled or connection failed
2515
+ this.debugTools.info('WALLET_CONNECTOR', 'No account found after visibility change - resetting state');
2516
+ clearTimeout(connectionTimeout);
2517
+ clearTimeout(safetyTimeout);
2518
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
2519
+ this.updateState({
2520
+ status: exports.ConnectionStatus.DISCONNECTED,
2521
+ wallet: null,
2522
+ error: new Error('Connection cancelled or failed'),
2523
+ });
2524
+ this.eventEmitter.emit(exports.WalletEvent.ERROR, new Error('Connection cancelled or failed'));
2525
+ }
2513
2526
  }
2514
2527
  catch (err) {
2515
2528
  this.debugTools.warn('WALLET_CONNECTOR', 'Failed to check account on visibility change', err);
2529
+ // On error checking account, reset state after a longer delay
2530
+ setTimeout(() => {
2531
+ if (this.state.status === exports.ConnectionStatus.CONNECTING) {
2532
+ this.debugTools.info('WALLET_CONNECTOR', 'Still connecting after visibility check error - resetting state');
2533
+ clearTimeout(connectionTimeout);
2534
+ clearTimeout(safetyTimeout);
2535
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
2536
+ this.updateState({
2537
+ status: exports.ConnectionStatus.DISCONNECTED,
2538
+ wallet: null,
2539
+ error: new Error('Connection timeout'),
2540
+ });
2541
+ this.eventEmitter.emit(exports.WalletEvent.ERROR, new Error('Connection timeout'));
2542
+ }
2543
+ }, 3000); // Give it 3 more seconds
2516
2544
  }
2517
2545
  }
2518
2546
  }, 1000);
@@ -7504,7 +7532,7 @@ const defaultStyles = {
7504
7532
  fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
7505
7533
  boxShadow: '0 4px 14px 0 rgba(51, 150, 255, 0.39)',
7506
7534
  }};
7507
- const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet', fetchTransactions, getExplorerUrl, fetchBalance: fetchBalanceProp, }) => {
7535
+ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet Now', fetchTransactions, getExplorerUrl, fetchBalance: fetchBalanceProp, }) => {
7508
7536
  // Provide a default fetchBalance if not passed
7509
7537
  const fetchBalance = fetchBalanceProp || (async (account) => {
7510
7538
  // Default: return zero balance with symbol based on chain
@@ -7636,12 +7664,15 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7636
7664
  });
7637
7665
  // Stable USD value updater - only updates if value changes significantly
7638
7666
  const setUsdValueStable = React.useCallback((newValue) => {
7639
- // Set USD value immediately - only skip if value is the same
7640
7667
  setUsdValue((prevValue) => {
7641
7668
  // If new value is null/undefined and we have a previous value, keep it
7642
7669
  if ((newValue === null || newValue === undefined) && prevValue !== null) {
7643
7670
  return prevValue;
7644
7671
  }
7672
+ // Only update if value actually changed
7673
+ if (prevValue === newValue) {
7674
+ return prevValue;
7675
+ }
7645
7676
  // Otherwise set the new value
7646
7677
  return newValue;
7647
7678
  });
@@ -7855,9 +7886,12 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7855
7886
  const walletsRef = React.useRef(null);
7856
7887
  // Initialize and subscribe to events
7857
7888
  React.useEffect(() => {
7889
+ let isMounted = true;
7858
7890
  // Get initial state
7859
7891
  const initialState = connector.getState();
7860
7892
  setStatus(initialState.status);
7893
+ // Safety: Clear isConnecting on mount (in case it was stuck from previous session)
7894
+ setIsConnecting(false);
7861
7895
  // Only set wallets once to prevent re-renders
7862
7896
  if (!walletsRef.current) {
7863
7897
  walletsRef.current = connector.getAllWallets();
@@ -7874,6 +7908,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7874
7908
  setBalanceLoading(false);
7875
7909
  }, 2000);
7876
7910
  connector.getAccount().then(async (acc) => {
7911
+ if (!isMounted)
7912
+ return;
7877
7913
  setAccount(acc);
7878
7914
  // Load balance when account is available
7879
7915
  if (acc && acc.chainType === 'ton') {
@@ -7884,10 +7920,14 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7884
7920
  fetchTONBalance(acc),
7885
7921
  fetchTONNFTs(acc)
7886
7922
  ]);
7923
+ if (!isMounted)
7924
+ return;
7887
7925
  clearTimeout(safetyTimeout);
7888
7926
  setIsInitializing(false);
7889
7927
  }
7890
7928
  catch (err) {
7929
+ if (!isMounted)
7930
+ return;
7891
7931
  clearTimeout(safetyTimeout);
7892
7932
  setIsInitializing(false);
7893
7933
  }
@@ -7896,6 +7936,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7896
7936
  try {
7897
7937
  setBalanceLoading(true);
7898
7938
  const hasRealBalance = await loadBalance();
7939
+ if (!isMounted)
7940
+ return;
7899
7941
  // Always stop loading state after balance fetch
7900
7942
  setBalanceLoading(false);
7901
7943
  clearTimeout(safetyTimeout);
@@ -7903,6 +7945,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7903
7945
  setIsInitializing(false);
7904
7946
  }
7905
7947
  catch (err) {
7948
+ if (!isMounted)
7949
+ return;
7906
7950
  setBalanceLoading(false);
7907
7951
  clearTimeout(safetyTimeout);
7908
7952
  // Always stop initializing on error
@@ -7914,6 +7958,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7914
7958
  const state = connector.getState();
7915
7959
  const stateTwcBalance = state.twcBalance;
7916
7960
  if (acc && stateTwcBalance && stateTwcBalance !== '0' && stateTwcBalance !== '0.00') {
7961
+ if (!isMounted)
7962
+ return;
7917
7963
  setTwcBalance(stateTwcBalance);
7918
7964
  setUsdValueStable(state.usdValue ?? null);
7919
7965
  // Save to cache
@@ -7927,6 +7973,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7927
7973
  // Try to load from cache as fallback
7928
7974
  const cached = loadTWCBalanceFromCache(acc.address);
7929
7975
  if (cached?.balance && cached.balance !== '0' && cached.balance !== '0.00') {
7976
+ if (!isMounted)
7977
+ return;
7930
7978
  setTwcBalance(cached.balance);
7931
7979
  setUsdValueStable(cached.usdValue);
7932
7980
  clearTimeout(safetyTimeout);
@@ -7936,6 +7984,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7936
7984
  // No real balance yet, but for EVM wallets, proactively fetch with retries
7937
7985
  if (acc.chainType === 'evm') {
7938
7986
  fetchTWCBalanceWithRetry(2, 200).then((result) => {
7987
+ if (!isMounted)
7988
+ return;
7939
7989
  if (result) {
7940
7990
  setTwcBalance(result.balance);
7941
7991
  setUsdValueStable(result.usdValue);
@@ -7952,6 +8002,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7952
8002
  setIsInitializing(false);
7953
8003
  }
7954
8004
  }).catch((error) => {
8005
+ if (!isMounted)
8006
+ return;
7955
8007
  // On error - show connected button with 0 balance
7956
8008
  setTwcBalance('0');
7957
8009
  clearTimeout(safetyTimeout);
@@ -7967,9 +8019,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7967
8019
  fetchTONBalance(acc),
7968
8020
  fetchTONNFTs(acc)
7969
8021
  ]).then(() => {
8022
+ if (!isMounted)
8023
+ return;
7970
8024
  clearTimeout(safetyTimeout);
7971
8025
  setIsInitializing(false);
7972
8026
  }).catch((err) => {
8027
+ if (!isMounted)
8028
+ return;
7973
8029
  clearTimeout(safetyTimeout);
7974
8030
  setIsInitializing(false);
7975
8031
  });
@@ -7977,6 +8033,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7977
8033
  else if (fetchBalance) {
7978
8034
  // For other non-EVM chains: If fetchBalance is provided, load balance and then stop initializing
7979
8035
  fetchTWCBalanceWithRetry(1, 100).then((result) => {
8036
+ if (!isMounted)
8037
+ return;
7980
8038
  if (result) {
7981
8039
  setTwcBalance(result.balance);
7982
8040
  setUsdValueStable(result.usdValue);
@@ -7988,12 +8046,16 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
7988
8046
  clearTimeout(safetyTimeout);
7989
8047
  setIsInitializing(false);
7990
8048
  }).catch(() => {
8049
+ if (!isMounted)
8050
+ return;
7991
8051
  // Stop initializing even on error
7992
8052
  clearTimeout(safetyTimeout);
7993
8053
  setIsInitializing(false);
7994
8054
  });
7995
8055
  }
7996
8056
  else {
8057
+ if (!isMounted)
8058
+ return;
7997
8059
  // No fetchBalance and no cached balance - stop initializing with 0 balance
7998
8060
  setTwcBalance('0');
7999
8061
  clearTimeout(safetyTimeout);
@@ -8004,6 +8066,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8004
8066
  }
8005
8067
  }
8006
8068
  }).catch((err) => {
8069
+ if (!isMounted)
8070
+ return;
8007
8071
  clearTimeout(safetyTimeout);
8008
8072
  setIsInitializing(false);
8009
8073
  // If we can't get account even though status is connected, disconnect
@@ -8016,9 +8080,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8016
8080
  setIsInitializing(true);
8017
8081
  // Set a shorter timeout to prevent stuck loading state
8018
8082
  initTimeout = setTimeout(() => {
8083
+ if (!isMounted)
8084
+ return;
8019
8085
  setIsInitializing(false);
8020
8086
  // If still no account after timeout, ensure we show connect button
8021
8087
  connector.getAccount().then((acc) => {
8088
+ if (!isMounted)
8089
+ return;
8022
8090
  if (!acc) {
8023
8091
  setAccount(null);
8024
8092
  // If status is disconnected and no account, ensure we're fully disconnected
@@ -8030,11 +8098,15 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8030
8098
  }
8031
8099
  }
8032
8100
  }).catch((err) => {
8101
+ if (!isMounted)
8102
+ return;
8033
8103
  setAccount(null);
8034
8104
  setStatus(exports.ConnectionStatus.DISCONNECTED);
8035
8105
  });
8036
8106
  }, 1500); // Reduced to 1.5 second timeout to prevent stuck loading
8037
8107
  connector.getAccount().then((acc) => {
8108
+ if (!isMounted)
8109
+ return;
8038
8110
  if (initTimeout)
8039
8111
  clearTimeout(initTimeout);
8040
8112
  setAccount(acc);
@@ -8049,6 +8121,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8049
8121
  }
8050
8122
  }
8051
8123
  }).catch((err) => {
8124
+ if (!isMounted)
8125
+ return;
8052
8126
  if (initTimeout)
8053
8127
  clearTimeout(initTimeout);
8054
8128
  setIsInitializing(false);
@@ -8062,7 +8136,11 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8062
8136
  }
8063
8137
  // Subscribe to events
8064
8138
  const unsubscribeAccount = connector.on(exports.WalletEvent.ACCOUNT_CHANGED, async (acc) => {
8139
+ if (!isMounted)
8140
+ return;
8065
8141
  setAccount(acc);
8142
+ // Clear isConnecting when account changes (connection succeeded)
8143
+ setIsConnecting(false);
8066
8144
  // Don't set isInitializing to false yet - wait for balance to load
8067
8145
  // Load cached balance for new account immediately
8068
8146
  if (acc?.address) {
@@ -8080,9 +8158,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8080
8158
  fetchTONBalance(acc),
8081
8159
  fetchTONNFTs(acc)
8082
8160
  ]);
8161
+ if (!isMounted)
8162
+ return;
8083
8163
  setIsInitializing(false);
8084
8164
  }
8085
8165
  catch (err) {
8166
+ if (!isMounted)
8167
+ return;
8086
8168
  setIsInitializing(false);
8087
8169
  }
8088
8170
  }
@@ -8090,9 +8172,13 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8090
8172
  setBalanceLoading(true);
8091
8173
  try {
8092
8174
  await loadBalance();
8175
+ if (!isMounted)
8176
+ return;
8093
8177
  setIsInitializing(false);
8094
8178
  }
8095
8179
  catch (err) {
8180
+ if (!isMounted)
8181
+ return;
8096
8182
  setIsInitializing(false);
8097
8183
  setBalanceLoading(false);
8098
8184
  }
@@ -8127,9 +8213,14 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8127
8213
  });
8128
8214
  const unsubscribeStatus = connector.on(exports.WalletEvent.STATUS_CHANGED, (newStatus) => {
8129
8215
  setStatus(newStatus);
8130
- // If status changes to disconnected, stop initializing
8216
+ // If status changes to disconnected, stop initializing and connecting
8131
8217
  if (newStatus === exports.ConnectionStatus.DISCONNECTED) {
8132
8218
  setIsInitializing(false);
8219
+ setIsConnecting(false);
8220
+ }
8221
+ // If status changes to connected, clear connecting state
8222
+ if (newStatus === exports.ConnectionStatus.CONNECTED) {
8223
+ setIsConnecting(false);
8133
8224
  }
8134
8225
  });
8135
8226
  const unsubscribeDisconnect = connector.on(exports.WalletEvent.DISCONNECTED, () => {
@@ -8210,6 +8301,7 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8210
8301
  }
8211
8302
  });
8212
8303
  return () => {
8304
+ isMounted = false;
8213
8305
  clearTimeout(initTimeout);
8214
8306
  unsubscribeAccount();
8215
8307
  unsubscribeStatus();
@@ -8355,16 +8447,23 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8355
8447
  }, [connector]);
8356
8448
  // Load balance when account changes
8357
8449
  React.useEffect(() => {
8450
+ let isActive = true;
8358
8451
  if (account && fetchBalance) {
8359
8452
  setBalanceLoading(true);
8360
8453
  loadBalance()
8361
8454
  .then(() => {
8455
+ if (!isActive)
8456
+ return;
8362
8457
  // Always stop initializing once balance request completes
8363
8458
  })
8364
8459
  .catch(() => {
8460
+ if (!isActive)
8461
+ return;
8365
8462
  // Ignore errors, we still clear loading states
8366
8463
  })
8367
8464
  .finally(() => {
8465
+ if (!isActive)
8466
+ return;
8368
8467
  setBalanceLoading(false);
8369
8468
  setIsInitializing(false);
8370
8469
  });
@@ -8397,6 +8496,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8397
8496
  // Only fetch if we don't have a balance yet
8398
8497
  if (!hasBalance) {
8399
8498
  fetchTWCBalanceWithRetry(2, 200).then((result) => {
8499
+ if (!isActive)
8500
+ return;
8400
8501
  if (result) {
8401
8502
  setTwcBalance(result.balance);
8402
8503
  setUsdValueStable(result.usdValue);
@@ -8412,6 +8513,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8412
8513
  setIsInitializing(false);
8413
8514
  }
8414
8515
  }).catch(() => {
8516
+ if (!isActive)
8517
+ return;
8415
8518
  setIsInitializing(false);
8416
8519
  });
8417
8520
  }
@@ -8432,22 +8535,33 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8432
8535
  }
8433
8536
  }
8434
8537
  }
8538
+ return () => {
8539
+ isActive = false;
8540
+ };
8435
8541
  // eslint-disable-next-line react-hooks/exhaustive-deps
8436
8542
  }, [account?.address, fetchBalance]);
8437
8543
  // Polling mechanism as fallback to ensure TWC balance is always fetched
8438
8544
  React.useEffect(() => {
8439
- if (!account || account.chainType !== 'evm' || isInitializing) {
8545
+ if (!account || account.chainType !== 'evm') {
8440
8546
  return;
8441
8547
  }
8442
- // If we don't have a balance yet, start polling
8443
- const hasBalance = twcBalance && twcBalance !== '0' && twcBalance !== '0.00';
8444
- if (!hasBalance) {
8548
+ // Check if we already have a balance
8549
+ const currentBalance = twcBalance;
8550
+ const hasBalance = currentBalance && currentBalance !== '0' && currentBalance !== '0.00';
8551
+ if (!hasBalance && !isInitializing) {
8445
8552
  // Poll every 2 seconds, up to 3 times (6 seconds total) - faster polling
8446
8553
  let pollCount = 0;
8447
8554
  const maxPolls = 3;
8555
+ let isActive = true;
8448
8556
  const pollInterval = setInterval(() => {
8557
+ if (!isActive) {
8558
+ clearInterval(pollInterval);
8559
+ return;
8560
+ }
8449
8561
  pollCount++;
8450
8562
  fetchTWCBalanceWithRetry(2, 200).then((result) => {
8563
+ if (!isActive)
8564
+ return;
8451
8565
  if (result) {
8452
8566
  setTwcBalance(result.balance);
8453
8567
  setUsdValueStable(result.usdValue);
@@ -8460,17 +8574,22 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8460
8574
  clearInterval(pollInterval);
8461
8575
  }
8462
8576
  }).catch((error) => {
8577
+ if (!isActive)
8578
+ return;
8463
8579
  if (pollCount >= maxPolls) {
8464
8580
  clearInterval(pollInterval);
8465
8581
  }
8466
8582
  });
8467
8583
  }, 2000); // Poll every 2 seconds (faster)
8468
8584
  return () => {
8585
+ isActive = false;
8469
8586
  clearInterval(pollInterval);
8470
8587
  };
8471
8588
  }
8589
+ return undefined;
8590
+ // Only depend on account address - check balance inside effect
8472
8591
  // eslint-disable-next-line react-hooks/exhaustive-deps
8473
- }, [account?.address, twcBalance, isInitializing]);
8592
+ }, [account?.address]);
8474
8593
  // Load transactions when details modal opens
8475
8594
  React.useEffect(() => {
8476
8595
  if (showDetailsModal && account) {
@@ -8480,6 +8599,11 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8480
8599
  const handleConnect = React.useCallback(async (walletType) => {
8481
8600
  setIsConnecting(true);
8482
8601
  setError(null);
8602
+ // Safety timeout: ensure isConnecting is cleared after 75 seconds maximum
8603
+ const safetyTimeout = setTimeout(() => {
8604
+ setIsConnecting(false);
8605
+ setIsInitializing(false);
8606
+ }, 75000);
8483
8607
  // Close modal BEFORE connecting for TON wallets and WalletConnect
8484
8608
  // so external modals can show
8485
8609
  const isTonWallet = ['tonkeeper', 'tonhub', 'mytonwallet'].includes(walletType);
@@ -8493,27 +8617,20 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8493
8617
  setShowSendModal(false);
8494
8618
  }
8495
8619
  try {
8496
- const result = await connector.connect(walletType);
8497
- // Set account from connection result immediately
8498
- if (result?.account) {
8499
- setAccount(result.account);
8500
- setIsInitializing(false);
8501
- }
8502
- else {
8503
- // Fallback: get account after connection
8504
- const newAccount = await connector.getAccount();
8505
- if (newAccount) {
8506
- setAccount(newAccount);
8507
- setIsInitializing(false);
8508
- }
8509
- }
8620
+ // connector.connect() returns void - state updates happen via events
8621
+ await connector.connect(walletType);
8622
+ // Connection succeeded - state is updated via ACCOUNT_CHANGED event
8623
+ // which also clears isConnecting
8510
8624
  // Close modal after connection for non-TON/walletconnect wallets
8511
8625
  if (!isTonWallet && !isWalletConnect) {
8512
8626
  setShowModal(false);
8513
8627
  }
8514
- setIsConnecting(false);
8628
+ // Clear safety timeout on success
8629
+ clearTimeout(safetyTimeout);
8515
8630
  }
8516
8631
  catch (err) {
8632
+ // Clear safety timeout on error
8633
+ clearTimeout(safetyTimeout);
8517
8634
  // Always clear connection state on error
8518
8635
  setAccount(null);
8519
8636
  setStatus(exports.ConnectionStatus.DISCONNECTED);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiwiflix-wallet-connector",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Multi-chain wallet connector for Tiwiflix supporting EVM, TON, Solana, and Tron wallets",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",