Compare commits

..
10 changed files with 180 additions and 50 deletions
+4
View File
@@ -17,6 +17,10 @@ NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-ma
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# TAG name of the current app version - TODO: bump to the latest upon release
NX_APP_VERSION=v0.21.2-core-0.72.14
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
+1
View File
@@ -1 +1,2 @@
export * from './announcement-banner';
export * from './upgrade-banner';
@@ -0,0 +1,84 @@
import { useMemo, useState } from 'react';
import { gt, prerelease } from 'semver';
import {
ReleasesFeed,
useEnvironment,
useReleases,
Networks,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import {
CopyWithTooltip,
ExternalLink,
Intent,
NotificationBanner,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
// v0.20.12-core-0.71.4 -> v0.20.12
// we need to strip the "core" suffix in order to determine whether a release
// is a pre-release (candidate); example: v.0.21.0-beta.1-core-0.71.4
const parseTagName = (tagName: string) => tagName.replace(/-core-[\d.]+$/i, '');
type UpgradeBannerProps = {
showVersionChange: boolean;
};
export const UpgradeBanner = ({ showVersionChange }: UpgradeBannerProps) => {
const [visible, setVisible] = useState(true);
const { data } = useReleases(ReleasesFeed.FrontEnd);
const { APP_VERSION, VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
const newest = useMemo(() => {
if (!APP_VERSION || !data) return undefined;
const newer = data.filter((r) => gt(r.tagName, APP_VERSION));
const valid =
// filter pre-releases on mainnet
VEGA_ENV === Networks.MAINNET
? newer?.filter((r) => !prerelease(parseTagName(r.tagName)))
: newer;
return valid.sort((a, b) => (gt(a.tagName, b.tagName) ? -1 : 1))[0];
}, [APP_VERSION, VEGA_ENV, data]);
if (!visible || !newest) {
return null;
}
return (
<NotificationBanner
intent={Intent.Warning}
onClose={() => {
setVisible(false);
}}
>
<div className="uppercase mb-1">
<ExternalLink href={CANONICAL_URL}>
{t('Upgrade to the latest version of Console')}
</ExternalLink>
</div>
<div data-testid="bookmark-message">
<a
className="underline"
href={newest.htmlUrl}
rel="noreferrer nofollow noopener"
target="_blank"
>
{t("View what's changed")}
</a>{' '}
{t(' or bookmark')}{' '}
<a className="underline" href={CANONICAL_URL}>
{t('console.vega.xyz')}
</a>{' '}
<CopyWithTooltip text={CANONICAL_URL}>
<button title={t('Copy %s', CANONICAL_URL)}>
<span className="sr-only">{t('Copy %s', CANONICAL_URL)}</span>
<VegaIcon size={14} name={VegaIconNames.COPY} />
</button>
</CopyWithTooltip>{' '}
{'to always see the latest version.'}
</div>
</NotificationBanner>
);
};
+2 -1
View File
@@ -38,7 +38,7 @@ import { AppLoader, DynamicLoader } from '../components/app-loader';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
import { AnnouncementBanner } from '../components/banner';
import { AnnouncementBanner, UpgradeBanner } from '../components/banner';
import { Navbar } from '../components/navbar';
import classNames from 'classnames';
import {
@@ -115,6 +115,7 @@ function AppBody({ Component }: AppProps) {
/>
<ProtocolUpgradeInProgressNotification />
<ViewingBanner />
<UpgradeBanner showVersionChange={true} />
</div>
<div data-testid={`pathname-${location.pathname}`}>
<Component />
@@ -155,8 +155,7 @@ const ConnectDialogContainer = ({
const isDesktopWalletRunning = useIsWalletServiceRunning(
walletUrl,
connectors['jsonRpc'],
appChainId
connectors['jsonRpc']
);
const snapStatus = useSnapStatus(
@@ -0,0 +1,41 @@
import { renderHook, waitFor } from '@testing-library/react';
import { JsonRpcConnector } from './connectors';
import { useIsWalletServiceRunning } from './use-is-wallet-service-running';
describe('useIsWalletServiceRunning', () => {
it('returns true if wallet is running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockResolvedValue(true);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(true);
});
});
it('returns false if wallet is not running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockRejectedValue(false);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(false);
});
});
});
@@ -0,0 +1,39 @@
import { useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connector: JsonRpcConnector | undefined
) => {
const [isRunning, setIsRunning] = useState<boolean | null>(null);
useEffect(() => {
if (!connector) return;
if (url && url !== connector.url) {
connector.url = url;
}
const check = async () => {
try {
// we are not checking wallet compatibility here, only that the wallet is running
await connector.checkCompat();
setIsRunning(true);
} catch {
setIsRunning(false);
}
};
// check immediately
check();
// check every second for quick feedback to the user
const interval = setInterval(check, 1000);
return () => {
clearInterval(interval);
};
}, [connector, url]);
return isRunning;
};
@@ -1,47 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
import { ClientErrors } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connector: JsonRpcConnector | undefined,
appChainId: string
) => {
const [run, setRun] = useState<boolean | null>(null);
const checkState = useCallback(async () => {
if (!connector) return false;
if (url && url !== connector.url) {
connector.url = url;
}
try {
await connector.checkCompat();
const chainIdResult = await connector.getChainId();
if (chainIdResult.chainID !== appChainId) {
throw ClientErrors.WRONG_NETWORK;
}
} catch (e) {
return false;
}
return true;
}, [connector, url, appChainId]);
useEffect(() => {
if (!connector) return;
let interval: NodeJS.Timeout;
checkState().then((value) => {
setRun(value);
interval = setInterval(async () => {
setRun(await checkState());
}, 1000 * 10);
});
return () => {
clearInterval(interval);
};
}, [checkState, connector]);
return run;
};
+1
View File
@@ -87,6 +87,7 @@
"recharts": "^2.1.2",
"recursive-key-filter": "^1.0.2",
"regenerator-runtime": "0.13.7",
"semver": "^7.5.1",
"toml": "^3.0.0",
"tslib": "^2.3.0",
"uuid": "^8.3.2",
+7
View File
@@ -22591,6 +22591,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.5.1:
version "7.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"
integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==
dependencies:
lru-cache "^6.0.0"
semver@^7.5.3:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"