@rango-dev/widget-embedded 0.20.1-next.21 → 0.20.1-next.23
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/QueueManager.d.ts.map +1 -1
- package/dist/components/AppRouter.d.ts.map +1 -1
- package/dist/components/AppRoutes.d.ts.map +1 -1
- package/dist/components/ConfirmWalletsModal/ConfirmWallets.styles.d.ts +3 -3
- package/dist/components/ConfirmWalletsModal/WalletList.d.ts.map +1 -1
- package/dist/components/Layout/Layout.d.ts.map +1 -1
- package/dist/components/NotificationContent/NotificationContent.d.ts.map +1 -1
- package/dist/components/Quote/Quote.styles.d.ts +2 -2
- package/dist/components/QuoteWarningsAndErrors/HighValueLossWarningModal.d.ts +1 -0
- package/dist/components/QuoteWarningsAndErrors/HighValueLossWarningModal.d.ts.map +1 -1
- package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.d.ts.map +1 -1
- package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.types.d.ts +1 -0
- package/dist/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.types.d.ts.map +1 -1
- package/dist/components/QuoteWarningsAndErrors/SlippageWariningModal.d.ts +1 -0
- package/dist/components/QuoteWarningsAndErrors/SlippageWariningModal.d.ts.map +1 -1
- package/dist/components/QuoteWarningsAndErrors/UnknownPriceWarningModal.d.ts +1 -0
- package/dist/components/QuoteWarningsAndErrors/UnknownPriceWarningModal.d.ts.map +1 -1
- package/dist/components/SwapDetails/SwapDetails.d.ts.map +1 -1
- package/dist/components/SwapDetailsModal/SwapDetailsModal.WalletState.d.ts.map +1 -1
- package/dist/components/SwapDetailsModal/SwapDetailsModal.d.ts.map +1 -1
- package/dist/components/SwapDetailsModal/SwapDetailsModal.types.d.ts +2 -0
- package/dist/components/SwapDetailsModal/SwapDetailsModal.types.d.ts.map +1 -1
- package/dist/components/common/ActivateTabAlert/ActivateTabAlert.d.ts +4 -0
- package/dist/components/common/ActivateTabAlert/ActivateTabAlert.d.ts.map +1 -0
- package/dist/components/common/ActivateTabAlert/ActivateTabAlert.types.d.ts +4 -0
- package/dist/components/common/ActivateTabAlert/ActivateTabAlert.types.d.ts.map +1 -0
- package/dist/components/common/ActivateTabAlert/index.d.ts +2 -0
- package/dist/components/common/ActivateTabAlert/index.d.ts.map +1 -0
- package/dist/components/common/ActivateTabModal/ActivateTabModal.d.ts +4 -0
- package/dist/components/common/ActivateTabModal/ActivateTabModal.d.ts.map +1 -0
- package/dist/components/common/ActivateTabModal/ActivateTabModal.types.d.ts +6 -0
- package/dist/components/common/ActivateTabModal/ActivateTabModal.types.d.ts.map +1 -0
- package/dist/components/common/ActivateTabModal/index.d.ts +2 -0
- package/dist/components/common/ActivateTabModal/index.d.ts.map +1 -0
- package/dist/constants/searchParams.d.ts +6 -6
- package/dist/constants/searchParams.d.ts.map +1 -1
- package/dist/containers/Widget/Widget.d.ts.map +1 -1
- package/dist/containers/WidgetInfo/WidgetInfo.d.ts.map +1 -1
- package/dist/containers/WidgetInfo/WidgetInfo.types.d.ts +2 -0
- package/dist/containers/WidgetInfo/WidgetInfo.types.d.ts.map +1 -1
- package/dist/hooks/useSwapInput.d.ts.map +1 -1
- package/dist/hooks/useSyncStoresWithConfig.d.ts.map +1 -1
- package/dist/hooks/useSyncUrlAndStore/index.d.ts +2 -0
- package/dist/hooks/useSyncUrlAndStore/index.d.ts.map +1 -0
- package/dist/hooks/useSyncUrlAndStore/useSyncUrlAndStore.d.ts +2 -0
- package/dist/hooks/useSyncUrlAndStore/useSyncUrlAndStore.d.ts.map +1 -0
- package/dist/hooks/useSyncUrlAndStore/useSyncUrlAndStore.helpers.d.ts +4 -0
- package/dist/hooks/useSyncUrlAndStore/useSyncUrlAndStore.helpers.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +4 -4
- package/dist/libs/tabManager/index.d.ts +3 -0
- package/dist/libs/tabManager/index.d.ts.map +1 -0
- package/dist/libs/tabManager/tabManager.constants.d.ts +4 -0
- package/dist/libs/tabManager/tabManager.constants.d.ts.map +1 -0
- package/dist/libs/tabManager/tabManager.d.ts +63 -0
- package/dist/libs/tabManager/tabManager.d.ts.map +1 -0
- package/dist/libs/tabManager/tabManager.types.d.ts +29 -0
- package/dist/libs/tabManager/tabManager.types.d.ts.map +1 -0
- package/dist/pages/ConfirmSwapPage.d.ts.map +1 -1
- package/dist/pages/HistoryPage.d.ts.map +1 -1
- package/dist/pages/Home.d.ts.map +1 -1
- package/dist/pages/SwapDetailsPage.d.ts.map +1 -1
- package/dist/pages/WalletsPage.d.ts.map +1 -1
- package/dist/store/ui.d.ts +12 -10
- package/dist/store/ui.d.ts.map +1 -1
- package/dist/utils/quote.d.ts +0 -1
- package/dist/utils/quote.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/QueueManager.tsx +4 -1
- package/src/components/AppRouter.tsx +1 -8
- package/src/components/AppRoutes.tsx +11 -0
- package/src/components/ConfirmWalletsModal/WalletList.tsx +3 -0
- package/src/components/Layout/Layout.tsx +37 -8
- package/src/components/NotificationContent/NotificationContent.tsx +0 -3
- package/src/components/QuoteWarningsAndErrors/HighValueLossWarningModal.tsx +3 -1
- package/src/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.tsx +3 -1
- package/src/components/QuoteWarningsAndErrors/QuoteWarningsAndErrors.types.ts +1 -0
- package/src/components/QuoteWarningsAndErrors/SlippageWariningModal.tsx +3 -1
- package/src/components/QuoteWarningsAndErrors/UnknownPriceWarningModal.tsx +3 -1
- package/src/components/SwapDetails/SwapDetails.tsx +3 -0
- package/src/components/SwapDetailsModal/SwapDetailsModal.WalletState.tsx +9 -1
- package/src/components/SwapDetailsModal/SwapDetailsModal.tsx +10 -2
- package/src/components/SwapDetailsModal/SwapDetailsModal.types.ts +2 -0
- package/src/components/common/ActivateTabAlert/ActivateTabAlert.tsx +26 -0
- package/src/components/common/ActivateTabAlert/ActivateTabAlert.types.ts +3 -0
- package/src/components/common/ActivateTabAlert/index.ts +1 -0
- package/src/components/common/ActivateTabModal/ActivateTabModal.tsx +31 -0
- package/src/components/common/ActivateTabModal/ActivateTabModal.types.ts +5 -0
- package/src/components/common/ActivateTabModal/index.ts +1 -0
- package/src/constants/searchParams.ts +6 -6
- package/src/containers/Wallets/Wallets.tsx +3 -3
- package/src/containers/Widget/Widget.tsx +6 -1
- package/src/containers/WidgetInfo/WidgetInfo.tsx +5 -1
- package/src/containers/WidgetInfo/WidgetInfo.types.ts +2 -0
- package/src/hooks/useSwapInput.ts +6 -1
- package/src/hooks/useSyncStoresWithConfig.ts +3 -17
- package/src/hooks/useSyncUrlAndStore/index.ts +1 -0
- package/src/hooks/useSyncUrlAndStore/useSyncUrlAndStore.helpers.ts +35 -0
- package/src/hooks/useSyncUrlAndStore/useSyncUrlAndStore.ts +135 -0
- package/src/libs/tabManager/index.ts +2 -0
- package/src/libs/tabManager/tabManager.constants.ts +3 -0
- package/src/libs/tabManager/tabManager.ts +222 -0
- package/src/libs/tabManager/tabManager.types.ts +28 -0
- package/src/pages/ConfirmSwapPage.tsx +6 -5
- package/src/pages/HistoryPage.tsx +1 -6
- package/src/pages/Home.tsx +12 -6
- package/src/pages/SwapDetailsPage.tsx +21 -7
- package/src/pages/WalletsPage.tsx +3 -0
- package/src/store/ui.ts +39 -11
- package/src/utils/quote.ts +0 -27
- package/dist/components/UpdateUrl.d.ts +0 -2
- package/dist/components/UpdateUrl.d.ts.map +0 -1
- package/src/components/UpdateUrl.tsx +0 -158
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PropTypes } from './ActivateTabAlert.types';
|
|
2
|
+
|
|
3
|
+
import { i18n } from '@lingui/core';
|
|
4
|
+
import { Alert, Button } from '@rango-dev/ui';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
export function ActivateTabAlert(props: PropTypes) {
|
|
8
|
+
return (
|
|
9
|
+
<Alert
|
|
10
|
+
action={
|
|
11
|
+
<Button
|
|
12
|
+
onClick={props.onActivateTab}
|
|
13
|
+
variant="contained"
|
|
14
|
+
size="xxsmall"
|
|
15
|
+
type="warning">
|
|
16
|
+
{i18n.t('Activate')}
|
|
17
|
+
</Button>
|
|
18
|
+
}
|
|
19
|
+
type="warning"
|
|
20
|
+
variant="alarm"
|
|
21
|
+
title={i18n.t(
|
|
22
|
+
'Another tab is open and handles transactions. You can activate this tab.'
|
|
23
|
+
)}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ActivateTabAlert } from './ActivateTabAlert';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PropTypes } from './ActivateTabModal.types';
|
|
2
|
+
|
|
3
|
+
import { i18n } from '@lingui/core';
|
|
4
|
+
import { Button, Divider, MessageBox, Modal } from '@rango-dev/ui';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { getContainer } from '../../../utils/common';
|
|
8
|
+
|
|
9
|
+
export function ActivateTabModal(props: PropTypes) {
|
|
10
|
+
const { open, onClose, onConfirm } = props;
|
|
11
|
+
return (
|
|
12
|
+
<Modal open={open} dismissible onClose={onClose} container={getContainer()}>
|
|
13
|
+
<MessageBox
|
|
14
|
+
title={i18n.t('Activate current tab')}
|
|
15
|
+
type="warning"
|
|
16
|
+
description={i18n.t(
|
|
17
|
+
'Currently, some transactions are running and being handled by other browser tab. If you activate this tab, all transactions that are already in the transaction sign step will expire.'
|
|
18
|
+
)}>
|
|
19
|
+
<Divider size={20} />
|
|
20
|
+
<Button
|
|
21
|
+
variant="contained"
|
|
22
|
+
size="large"
|
|
23
|
+
type="primary"
|
|
24
|
+
fullWidth
|
|
25
|
+
onClick={onConfirm}>
|
|
26
|
+
{i18n.t('Confirm')}
|
|
27
|
+
</Button>
|
|
28
|
+
</MessageBox>
|
|
29
|
+
</Modal>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ActivateTabModal } from './ActivateTabModal';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export enum SearchParams {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
FROM_BLOCKCHAIN = 'fromBlockchain',
|
|
3
|
+
FROM_TOKEN = 'fromToken',
|
|
4
|
+
TO_BLOCKCHAIN = 'toBlockchain',
|
|
5
|
+
TO_TOKEN = 'toToken',
|
|
6
|
+
FROM_AMOUNT = 'fromAmount',
|
|
7
|
+
AUTO_CONNECT = 'autoConnect',
|
|
8
8
|
}
|
|
@@ -13,9 +13,9 @@ import { Events, Provider } from '@rango-dev/wallets-react';
|
|
|
13
13
|
import { isEvmBlockchain } from 'rango-sdk';
|
|
14
14
|
import React, { createContext, useEffect, useRef, useState } from 'react';
|
|
15
15
|
|
|
16
|
-
import { useSyncStoresWithConfig } from '../../hooks/useSyncStoresWithConfig';
|
|
17
16
|
import { useWalletProviders } from '../../hooks/useWalletProviders';
|
|
18
17
|
import { AppStoreProvider, useAppStore } from '../../store/AppStore';
|
|
18
|
+
import { useUiStore } from '../../store/ui';
|
|
19
19
|
import { useWalletsStore } from '../../store/wallets';
|
|
20
20
|
import {
|
|
21
21
|
prepareAccountsForWalletStore,
|
|
@@ -39,7 +39,6 @@ function Main(props: PropsWithChildren<PropTypes>) {
|
|
|
39
39
|
const { providers } = useWalletProviders(props.config.wallets, walletOptions);
|
|
40
40
|
const { connectWallet, disconnectWallet } = useWalletsStore();
|
|
41
41
|
const onConnectWalletHandler = useRef<OnConnectHandler>();
|
|
42
|
-
useSyncStoresWithConfig();
|
|
43
42
|
|
|
44
43
|
useEffect(() => {
|
|
45
44
|
void fetchMeta().catch(console.log);
|
|
@@ -101,6 +100,7 @@ function Main(props: PropsWithChildren<PropTypes>) {
|
|
|
101
100
|
props.onUpdateState(type, event, value, state, meta);
|
|
102
101
|
}
|
|
103
102
|
};
|
|
103
|
+
const isActiveTab = useUiStore.use.isActiveTab();
|
|
104
104
|
|
|
105
105
|
useEffect(() => {
|
|
106
106
|
if (accounts.length) {
|
|
@@ -120,7 +120,7 @@ function Main(props: PropsWithChildren<PropTypes>) {
|
|
|
120
120
|
allBlockChains={blockchains}
|
|
121
121
|
providers={providers}
|
|
122
122
|
onUpdateState={onUpdateState}
|
|
123
|
-
autoConnect>
|
|
123
|
+
autoConnect={!!isActiveTab}>
|
|
124
124
|
{props.children}
|
|
125
125
|
</Provider>
|
|
126
126
|
</WidgetContext.Provider>
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type { WidgetProps } from './Widget.types';
|
|
2
2
|
import type { PropsWithChildren } from 'react';
|
|
3
3
|
|
|
4
|
-
import React from 'react';
|
|
4
|
+
import React, { useEffect } from 'react';
|
|
5
5
|
|
|
6
6
|
import { DEFAULT_CONFIG } from '../../store/slices/config';
|
|
7
|
+
import { destroyTabManager } from '../../store/ui';
|
|
7
8
|
import { Main } from '../App';
|
|
8
9
|
import { WidgetProvider } from '../WidgetProvider';
|
|
9
10
|
|
|
10
11
|
export function Widget(props: PropsWithChildren<WidgetProps>) {
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
return destroyTabManager;
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
11
16
|
if (!props.config?.externalWallets) {
|
|
12
17
|
return (
|
|
13
18
|
<WidgetProvider config={props.config ?? DEFAULT_CONFIG}>
|
|
@@ -7,6 +7,7 @@ import { useLanguage } from '../../hooks/useLanguage';
|
|
|
7
7
|
import { useAppStore } from '../../store/AppStore';
|
|
8
8
|
import { useNotificationStore } from '../../store/notification';
|
|
9
9
|
import { useQuoteStore } from '../../store/quote';
|
|
10
|
+
import { setCurrentTabAsActive, useUiStore } from '../../store/ui';
|
|
10
11
|
import { useWalletsStore } from '../../store/wallets';
|
|
11
12
|
import { calculateWalletUsdValue } from '../../utils/wallets';
|
|
12
13
|
|
|
@@ -18,6 +19,7 @@ export const WidgetInfoContext = createContext<
|
|
|
18
19
|
|
|
19
20
|
export function WidgetInfo(props: React.PropsWithChildren) {
|
|
20
21
|
const { manager } = useManager();
|
|
22
|
+
const isActiveTab = useUiStore.use.isActiveTab();
|
|
21
23
|
const retrySwap = useQuoteStore.use.retry();
|
|
22
24
|
const history = new WidgetHistory(manager, { retrySwap });
|
|
23
25
|
const details = useWalletsStore.use.connectedWallets();
|
|
@@ -32,7 +34,9 @@ export function WidgetInfo(props: React.PropsWithChildren) {
|
|
|
32
34
|
const notifications = useNotificationStore().getUnreadNotifications();
|
|
33
35
|
|
|
34
36
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
|
35
|
-
const value = {
|
|
37
|
+
const value: WidgetInfoContextInterface = {
|
|
38
|
+
isActiveTab,
|
|
39
|
+
setCurrentTabAsActive,
|
|
36
40
|
history,
|
|
37
41
|
wallets: {
|
|
38
42
|
isLoading,
|
|
@@ -6,6 +6,8 @@ import type { Notification } from '../../types/notification';
|
|
|
6
6
|
import type { BlockchainMeta, SwapperMeta, Token } from 'rango-sdk';
|
|
7
7
|
|
|
8
8
|
export interface WidgetInfoContextInterface {
|
|
9
|
+
isActiveTab: boolean;
|
|
10
|
+
setCurrentTabAsActive: () => void;
|
|
9
11
|
history: WidgetHistory;
|
|
10
12
|
wallets: {
|
|
11
13
|
details: ConnectedWallet[];
|
|
@@ -59,8 +59,9 @@ export function useSwapInput(): UseSwapInput {
|
|
|
59
59
|
affiliateRef,
|
|
60
60
|
affiliateWallets,
|
|
61
61
|
disabledLiquiditySources,
|
|
62
|
+
fetchStatus,
|
|
62
63
|
} = useAppStore();
|
|
63
|
-
const [loading, setLoading] = useState(
|
|
64
|
+
const [loading, setLoading] = useState(true);
|
|
64
65
|
const [error, setError] = useState<QuoteError | null>(null);
|
|
65
66
|
const [warning, setWarning] = useState<QuoteWarning | null>(null);
|
|
66
67
|
const userSlippage = customSlippage ?? slippage;
|
|
@@ -148,6 +149,9 @@ export function useSwapInput(): UseSwapInput {
|
|
|
148
149
|
);
|
|
149
150
|
|
|
150
151
|
useEffect(() => {
|
|
152
|
+
if (fetchStatus !== 'success') {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
151
155
|
if (!isPositiveNumber(inputAmount) || inputUsdValue?.eq(0)) {
|
|
152
156
|
resetState(false);
|
|
153
157
|
cancelFetch();
|
|
@@ -175,6 +179,7 @@ export function useSwapInput(): UseSwapInput {
|
|
|
175
179
|
});
|
|
176
180
|
return cancelFetch;
|
|
177
181
|
}, [
|
|
182
|
+
fetchStatus,
|
|
178
183
|
inputAmount,
|
|
179
184
|
fromToken?.symbol,
|
|
180
185
|
fromToken?.address,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Asset } from 'rango-sdk';
|
|
2
2
|
|
|
3
|
-
import { useEffect,
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useAppStore } from '../store/AppStore';
|
|
6
6
|
import { useQuoteStore } from '../store/quote';
|
|
@@ -44,14 +44,7 @@ export function useSyncStoresWithConfig() {
|
|
|
44
44
|
setInputAmount(config?.amount?.toString() || '');
|
|
45
45
|
}, [config?.amount]);
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
* We update quote tokens in two scenarios.
|
|
49
|
-
* If default values exist in widget config or search parameters exist in URL.
|
|
50
|
-
* The logic for updating quote store exists in two useEffect in two locations, and they run in order.If default tokens for quote exist in widget config and also exist in URL, widget config values take precedence over URL values, and URL search parameters don’t affect the widget.
|
|
51
|
-
* Using useLayoutEffect causes widget config values to apply first.
|
|
52
|
-
* We may consider replacing this with a better solution in the future.
|
|
53
|
-
*/
|
|
54
|
-
useLayoutEffect(() => {
|
|
47
|
+
useEffect(() => {
|
|
55
48
|
if (fetchMetaStatus === 'success') {
|
|
56
49
|
const chain = blockchains.find(
|
|
57
50
|
(chain) => chain.name === config?.from?.blockchain
|
|
@@ -100,14 +93,7 @@ export function useSyncStoresWithConfig() {
|
|
|
100
93
|
}
|
|
101
94
|
}, [toTokensConfig, toBlockchainsConfig]);
|
|
102
95
|
|
|
103
|
-
|
|
104
|
-
* We update quote tokens in two scenarios.
|
|
105
|
-
* If default values exist in widget config or search parameters exist in URL.
|
|
106
|
-
* The logic for updating quote store exists in two useEffect in two locations, and they run in order.If default tokens for quote exist in widget config and also exist in URL, widget config values take precedence over URL values, and URL search parameters don’t affect the widget.
|
|
107
|
-
* Using useLayoutEffect causes widget config values to apply first.
|
|
108
|
-
* We may consider replacing this with a better solution in the future.
|
|
109
|
-
*/
|
|
110
|
-
useLayoutEffect(() => {
|
|
96
|
+
useEffect(() => {
|
|
111
97
|
if (fetchMetaStatus === 'success') {
|
|
112
98
|
const chain = blockchains.find(
|
|
113
99
|
(chain) => chain.name === config?.to?.blockchain
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSyncUrlAndStore } from './useSyncUrlAndStore';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BlockchainMeta, Token } from 'rango-sdk';
|
|
2
|
+
|
|
3
|
+
export function searchParamToToken(
|
|
4
|
+
tokens: Token[],
|
|
5
|
+
searchParam: string | null,
|
|
6
|
+
chain: BlockchainMeta | null
|
|
7
|
+
): Token | null {
|
|
8
|
+
if (!chain) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return (
|
|
12
|
+
tokens.find((token) => {
|
|
13
|
+
const symbolAndAddress = searchParam?.split('--');
|
|
14
|
+
if (symbolAndAddress?.length === 1) {
|
|
15
|
+
return (
|
|
16
|
+
token.symbol === symbolAndAddress[0] &&
|
|
17
|
+
token.address === null &&
|
|
18
|
+
token.blockchain === chain.name
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
token.symbol === symbolAndAddress?.[0] &&
|
|
23
|
+
token.address === symbolAndAddress?.[1] &&
|
|
24
|
+
token.blockchain === chain.name
|
|
25
|
+
);
|
|
26
|
+
}) || null
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function tokenToSearchParam(token: Token | null): string | undefined {
|
|
31
|
+
if (token) {
|
|
32
|
+
return `${token.symbol}${token.address ? `--${token.address}` : ''}`;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useInRouterContext,
|
|
4
|
+
useLocation,
|
|
5
|
+
useSearchParams,
|
|
6
|
+
} from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
import { SearchParams } from '../../constants/searchParams';
|
|
9
|
+
import { useAppStore } from '../../store/AppStore';
|
|
10
|
+
import { useQuoteStore } from '../../store/quote';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
searchParamToToken,
|
|
14
|
+
tokenToSearchParam,
|
|
15
|
+
} from './useSyncUrlAndStore.helpers';
|
|
16
|
+
|
|
17
|
+
export function useSyncUrlAndStore() {
|
|
18
|
+
const location = useLocation();
|
|
19
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
20
|
+
const {
|
|
21
|
+
fromBlockchain,
|
|
22
|
+
toBlockchain,
|
|
23
|
+
fromToken,
|
|
24
|
+
toToken,
|
|
25
|
+
inputAmount,
|
|
26
|
+
setFromBlockchain,
|
|
27
|
+
setToBlockchain,
|
|
28
|
+
setFromToken,
|
|
29
|
+
setToToken,
|
|
30
|
+
setInputAmount,
|
|
31
|
+
} = useQuoteStore();
|
|
32
|
+
const fetchMetaStatus = useAppStore().fetchStatus;
|
|
33
|
+
const blockchains = useAppStore().blockchains();
|
|
34
|
+
const tokens = useAppStore().tokens();
|
|
35
|
+
const isInRouterContext = useInRouterContext();
|
|
36
|
+
|
|
37
|
+
const getUrlSearchParams = () => {
|
|
38
|
+
const fromAmount = searchParams.get(SearchParams.FROM_AMOUNT);
|
|
39
|
+
const fromBlockchain = searchParams.get(SearchParams.FROM_BLOCKCHAIN);
|
|
40
|
+
const fromToken = searchParams.get(SearchParams.FROM_TOKEN);
|
|
41
|
+
const toBlockchain = searchParams.get(SearchParams.TO_BLOCKCHAIN);
|
|
42
|
+
const toToken = searchParams.get(SearchParams.TO_TOKEN);
|
|
43
|
+
const autoConnect = searchParams.get(SearchParams.AUTO_CONNECT);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
fromAmount,
|
|
47
|
+
fromBlockchain,
|
|
48
|
+
fromToken,
|
|
49
|
+
toBlockchain,
|
|
50
|
+
toToken,
|
|
51
|
+
autoConnect,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const updateUrlSearchParams = (
|
|
56
|
+
searchParams: Record<string, string | undefined>
|
|
57
|
+
) => {
|
|
58
|
+
for (const property in searchParams) {
|
|
59
|
+
if (!searchParams[property]) {
|
|
60
|
+
delete searchParams[property];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
setSearchParams(searchParams as Record<string, string>, { replace: true });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const { autoConnect } = getUrlSearchParams();
|
|
68
|
+
if (isInRouterContext && fetchMetaStatus === 'success') {
|
|
69
|
+
updateUrlSearchParams({
|
|
70
|
+
[SearchParams.FROM_BLOCKCHAIN]: fromBlockchain?.name,
|
|
71
|
+
[SearchParams.FROM_TOKEN]: tokenToSearchParam(fromToken),
|
|
72
|
+
[SearchParams.TO_BLOCKCHAIN]: toBlockchain?.name,
|
|
73
|
+
[SearchParams.TO_TOKEN]: tokenToSearchParam(toToken),
|
|
74
|
+
[SearchParams.FROM_AMOUNT]: inputAmount,
|
|
75
|
+
[SearchParams.AUTO_CONNECT]: autoConnect ?? undefined,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}, [
|
|
79
|
+
location.pathname,
|
|
80
|
+
inputAmount,
|
|
81
|
+
fromBlockchain,
|
|
82
|
+
fromToken,
|
|
83
|
+
toBlockchain,
|
|
84
|
+
toToken,
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!isInRouterContext) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const searchParams = getUrlSearchParams();
|
|
92
|
+
if (searchParams.fromAmount) {
|
|
93
|
+
setInputAmount(searchParams.fromAmount);
|
|
94
|
+
}
|
|
95
|
+
if (fetchMetaStatus === 'success') {
|
|
96
|
+
const fromBlockchain = blockchains.find(
|
|
97
|
+
(blockchain) => blockchain.name === searchParams.fromBlockchain
|
|
98
|
+
);
|
|
99
|
+
const fromToken = searchParamToToken(
|
|
100
|
+
tokens,
|
|
101
|
+
searchParams.fromToken,
|
|
102
|
+
fromBlockchain ?? null
|
|
103
|
+
);
|
|
104
|
+
const toBlockchain = blockchains.find(
|
|
105
|
+
(blockchain) => blockchain.name === searchParams.toBlockchain
|
|
106
|
+
);
|
|
107
|
+
const toToken = searchParamToToken(
|
|
108
|
+
tokens,
|
|
109
|
+
searchParams.toToken,
|
|
110
|
+
toBlockchain ?? null
|
|
111
|
+
);
|
|
112
|
+
if (!!fromBlockchain) {
|
|
113
|
+
setFromBlockchain(fromBlockchain);
|
|
114
|
+
if (!!fromToken) {
|
|
115
|
+
setFromToken({
|
|
116
|
+
token: fromToken,
|
|
117
|
+
meta: {
|
|
118
|
+
blockchains: blockchains,
|
|
119
|
+
tokens: tokens,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!!toBlockchain) {
|
|
125
|
+
setToBlockchain(toBlockchain);
|
|
126
|
+
if (!!toToken) {
|
|
127
|
+
setToToken({
|
|
128
|
+
token: toToken,
|
|
129
|
+
meta: { blockchains: blockchains, tokens: tokens },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, [fetchMetaStatus]);
|
|
135
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Events,
|
|
3
|
+
ID,
|
|
4
|
+
Message,
|
|
5
|
+
TabManagerInterface,
|
|
6
|
+
} from './tabManager.types';
|
|
7
|
+
|
|
8
|
+
import { CHANNEL_NAME, MAXIMUM_TAB_ID, TIMEOUT } from './tabManager.constants';
|
|
9
|
+
|
|
10
|
+
export class TabManager implements TabManagerInterface {
|
|
11
|
+
private tabId: ID;
|
|
12
|
+
private state: 'not-initiated' | 'not-claimed' | 'claimed' = 'not-initiated';
|
|
13
|
+
private lastTryClaim: number | undefined;
|
|
14
|
+
private events: Events = {};
|
|
15
|
+
private channel: BroadcastChannel;
|
|
16
|
+
|
|
17
|
+
constructor(events: Events) {
|
|
18
|
+
this.channel = new BroadcastChannel(CHANNEL_NAME);
|
|
19
|
+
this.tabId = Math.trunc(Math.random() * MAXIMUM_TAB_ID);
|
|
20
|
+
this.events = events;
|
|
21
|
+
this.initEvents();
|
|
22
|
+
void this.tryClaim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This is applicable when a user in an inactive tab seeks to activate that tab.
|
|
27
|
+
*/
|
|
28
|
+
forceClaim = () => {
|
|
29
|
+
if (!this.isClaimed()) {
|
|
30
|
+
const message: Message = { name: 'force-claim', candidateId: this.tabId };
|
|
31
|
+
this.channel.postMessage(message);
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
if (!this.isClaimed()) {
|
|
34
|
+
this.claim();
|
|
35
|
+
}
|
|
36
|
+
}, TIMEOUT);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
isClaimed = () => {
|
|
41
|
+
return this.state === 'claimed';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* remove event listeners
|
|
46
|
+
*/
|
|
47
|
+
destroy = () => {
|
|
48
|
+
this.channel.removeEventListener('message', this.handleMessageEvent);
|
|
49
|
+
document.removeEventListener(
|
|
50
|
+
'visibilitychange',
|
|
51
|
+
this.handleVisibilityChange
|
|
52
|
+
);
|
|
53
|
+
document.removeEventListener('resume', this.handleResume);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reacting to messages and window events to claim.
|
|
58
|
+
*/
|
|
59
|
+
private initEvents() {
|
|
60
|
+
this.channel.addEventListener('message', this.handleMessageEvent);
|
|
61
|
+
|
|
62
|
+
document.addEventListener('visibilitychange', this.handleVisibilityChange);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The "resume" event is currently supported only in certain browsers.
|
|
66
|
+
* It is triggered when a previously frozen tab is unfrozen and resumes execution.
|
|
67
|
+
* To avoid having multiple active tabs,
|
|
68
|
+
* if the current tab is active after the "resume" event,
|
|
69
|
+
* we send a signal to other tabs to verify if any of them currently claim to be active.
|
|
70
|
+
* If this is the case, we deactivate the current tab.
|
|
71
|
+
* https://developer.chrome.com/docs/web-platform/page-lifecycle-api
|
|
72
|
+
*/
|
|
73
|
+
document.addEventListener('resume', this.handleResume);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Runs after a `ping` message and checks if current tab is claimed the tabs,
|
|
78
|
+
* if yes, it responds with `pong`, If not, it doesn't do anything.
|
|
79
|
+
*
|
|
80
|
+
* Note 1: If we receive a `ping` message,
|
|
81
|
+
* it means a tab is trying to claim tabs directly, usually it's the active tab.
|
|
82
|
+
*
|
|
83
|
+
* Note 2: If multiple tabs are opening at once, all of them are broadcasting `ping`
|
|
84
|
+
* and eventually all of them will `claim` the queue after the TIMEOUT.
|
|
85
|
+
* For avoiding this, we will accepts the oldest broadcasted ping. (`isPingSentBefore`)
|
|
86
|
+
*/
|
|
87
|
+
private claimedByCurrentTab(pingAt: number) {
|
|
88
|
+
const isPingSentBefore = this.lastTryClaim && this.lastTryClaim < pingAt;
|
|
89
|
+
|
|
90
|
+
if (this.isClaimed() || isPingSentBefore) {
|
|
91
|
+
const message: Message = {
|
|
92
|
+
name: 'pong',
|
|
93
|
+
};
|
|
94
|
+
this.channel.postMessage(message);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private handleMessageEvent = (event: MessageEvent<Message>) => {
|
|
100
|
+
const name = event.data.name;
|
|
101
|
+
|
|
102
|
+
switch (name) {
|
|
103
|
+
case 'ping':
|
|
104
|
+
this.claimedByCurrentTab(event.data.pingAt);
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case 'pong':
|
|
108
|
+
this.alreadyClaimedByAnotherTab();
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case 'force-claim':
|
|
112
|
+
this.forceRelease(event.data.candidateId);
|
|
113
|
+
break;
|
|
114
|
+
|
|
115
|
+
case 'force-release':
|
|
116
|
+
if (this.tabId === event.data.candidateId) {
|
|
117
|
+
this.claim();
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`${name} is not supported.`);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
private handleVisibilityChange = async () => {
|
|
127
|
+
if (document.visibilityState === 'visible') {
|
|
128
|
+
await this.tryClaim();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
private handleResume = async () => {
|
|
133
|
+
if (this.isClaimed()) {
|
|
134
|
+
await this.tryClaim();
|
|
135
|
+
if (!this.isClaimed()) {
|
|
136
|
+
this.events.onRelease?.();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Runs after receiving `pong` message, which means one of tabs already claimed the tabs.
|
|
142
|
+
*/
|
|
143
|
+
private alreadyClaimedByAnotherTab() {
|
|
144
|
+
this.resetLastCheck();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Try to send a `ping` message to know if the tabs `claimed` or not.
|
|
149
|
+
* On constructing instance, or whenever page is active we will try.
|
|
150
|
+
*/
|
|
151
|
+
private async tryClaim() {
|
|
152
|
+
/**
|
|
153
|
+
* Try to ask from possible other running tabs.
|
|
154
|
+
*/
|
|
155
|
+
this.setLastCheck();
|
|
156
|
+
return new Promise<void>((resolve) => {
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
this.check();
|
|
159
|
+
resolve();
|
|
160
|
+
}, TIMEOUT);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* If `lastCheck` is exist, it means there is no other tabs which `claimed` the tabs.
|
|
166
|
+
* And we mark the current tab as `claimed`
|
|
167
|
+
*/
|
|
168
|
+
private check() {
|
|
169
|
+
if (this.state === 'not-initiated') {
|
|
170
|
+
this.events.onInit?.();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
*it means we didn't get any response from other tabs, this tab can claim.
|
|
174
|
+
*/
|
|
175
|
+
if (this.lastTryClaim) {
|
|
176
|
+
this.claim();
|
|
177
|
+
this.resetLastCheck();
|
|
178
|
+
} else {
|
|
179
|
+
this.state = 'not-claimed';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private claim() {
|
|
184
|
+
this.state = 'claimed';
|
|
185
|
+
this.events.onClaim?.();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Setting current time as `lastCheck` and broadcast a `ping` message event.
|
|
190
|
+
*/
|
|
191
|
+
private setLastCheck() {
|
|
192
|
+
this.lastTryClaim = Date.now();
|
|
193
|
+
const message: Message = {
|
|
194
|
+
name: 'ping',
|
|
195
|
+
pingAt: this.lastTryClaim,
|
|
196
|
+
};
|
|
197
|
+
this.channel.postMessage(message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private resetLastCheck() {
|
|
201
|
+
this.lastTryClaim = undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Upon receipt of a "force-claim" message,
|
|
206
|
+
* the presently active tab is expected to deactivate itself and initiate any attached callback associated with this event.
|
|
207
|
+
*/
|
|
208
|
+
private forceRelease(nextActiveTab: ID) {
|
|
209
|
+
if (this.isClaimed()) {
|
|
210
|
+
this.state = 'not-claimed';
|
|
211
|
+
|
|
212
|
+
this.events.onRelease?.();
|
|
213
|
+
|
|
214
|
+
const message: Message = {
|
|
215
|
+
name: 'force-release',
|
|
216
|
+
candidateId: nextActiveTab,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
this.channel.postMessage(message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|