Commit 9178696c authored by tom's avatar tom

add socket

parent 8b635e47
import type { Channel } from 'phoenix'; import type { Channel } from 'phoenix';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import type { NewBlockSocketResponse } from 'types/api/block'; import type { NewBlockSocketResponse } from 'types/api/block';
export type SocketMessageParams = SocketMessage.NewBlock | export type SocketMessageParams = SocketMessage.NewBlock |
...@@ -19,16 +20,6 @@ interface SocketMessageParamsGeneric<Event extends string | undefined, Payload e ...@@ -19,16 +20,6 @@ interface SocketMessageParamsGeneric<Event extends string | undefined, Payload e
handler: (payload: Payload) => void; handler: (payload: Payload) => void;
} }
export interface AddressCoinBalancePayload {
coin_balance: {
block_number: number;
block_timestamp?: string;
delta?: string;
transaction_hash?: string | null;
value?: string;
};
}
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
export namespace SocketMessage { export namespace SocketMessage {
export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>; export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>;
...@@ -40,6 +31,6 @@ export namespace SocketMessage { ...@@ -40,6 +31,6 @@ export namespace SocketMessage {
export type AddressCurrentCoinBalance = export type AddressCurrentCoinBalance =
SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>; SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>;
export type AddressTokenBalance = SocketMessageParamsGeneric<'token_balance', { block_number: number }>; export type AddressTokenBalance = SocketMessageParamsGeneric<'token_balance', { block_number: number }>;
export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', AddressCoinBalancePayload>; export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', { coin_balance: AddressCoinBalanceHistoryItem }>;
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>; export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
} }
export const base = {
block_number: 30367643,
block_timestamp: '2022-12-11T17:55:20Z',
delta: '-5568096000000000',
transaction_hash: null,
value: '107014805905725000000',
};
...@@ -2,7 +2,7 @@ import type { TestFixture, Page } from '@playwright/test'; ...@@ -2,7 +2,7 @@ import type { TestFixture, Page } from '@playwright/test';
import type { WebSocket } from 'ws'; import type { WebSocket } from 'ws';
import { WebSocketServer } from 'ws'; import { WebSocketServer } from 'ws';
import type { AddressCoinBalancePayload } from 'lib/socket/types'; import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import type { NewBlockSocketResponse } from 'types/api/block'; import type { NewBlockSocketResponse } from 'types/api/block';
type ReturnType = () => Promise<WebSocket>; type ReturnType = () => Promise<WebSocket>;
...@@ -54,7 +54,7 @@ export const joinChannel = async(socket: WebSocket, channelName: string) => { ...@@ -54,7 +54,7 @@ export const joinChannel = async(socket: WebSocket, channelName: string) => {
}); });
}; };
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'coin_balance', payload: AddressCoinBalancePayload): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'coin_balance', payload: { coin_balance: AddressCoinBalanceHistoryItem }): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'token_balance', payload: { block_number: number }): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'token_balance', payload: { block_number: number }): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transaction', payload: { transaction: number }): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transaction', payload: { transaction: number }): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'pending_transaction', payload: { pending_transaction: number }): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'pending_transaction', payload: { pending_transaction: number }): void;
......
import { useQueryClient } from '@tanstack/react-query';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { Address } from 'types/api/address'; import type { SocketMessage } from 'lib/socket/types';
import type { Address, AddressCoinBalanceHistoryResponse } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import SocketAlert from 'ui/shared/SocketAlert';
import AddressCoinBalanceChart from './coinBalance/AddressCoinBalanceChart'; import AddressCoinBalanceChart from './coinBalance/AddressCoinBalanceChart';
import AddressCoinBalanceHistory from './coinBalance/AddressCoinBalanceHistory'; import AddressCoinBalanceHistory from './coinBalance/AddressCoinBalanceHistory';
...@@ -11,10 +19,58 @@ interface Props { ...@@ -11,10 +19,58 @@ interface Props {
} }
const AddressCoinBalance = ({ addressQuery }: Props) => { const AddressCoinBalance = ({ addressQuery }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(false);
const queryClient = useQueryClient();
const coinBalanceQuery = useQueryWithPages({
apiPath: `/node-api/addresses/${ addressQuery.data?.hash }/coin-balance-history`,
queryName: QueryKeys.addressCoinBalanceHistory,
options: {
enabled: Boolean(addressQuery.data),
},
});
const handleSocketError = React.useCallback(() => {
setSocketAlert(true);
}, []);
const handleNewSocketMessage: SocketMessage.AddressCoinBalance['handler'] = React.useCallback((payload) => {
setSocketAlert(false);
queryClient.setQueryData(
[ QueryKeys.addressCoinBalanceHistory, { page: coinBalanceQuery.pagination.page } ],
(prevData: AddressCoinBalanceHistoryResponse | undefined) => {
if (!prevData) {
return;
}
return {
...prevData,
items: [
payload.coin_balance,
...prevData.items,
],
};
});
}, [ coinBalanceQuery.pagination.page, queryClient ]);
const channel = useSocketChannel({
topic: `addresses:${ addressQuery.data?.hash.toLowerCase() }`,
onSocketClose: handleSocketError,
onSocketError: handleSocketError,
isDisabled: addressQuery.isLoading || addressQuery.isError || !addressQuery.data.hash || coinBalanceQuery.pagination.page !== 1,
});
useSocketMessage({
channel,
event: 'coin_balance',
handler: handleNewSocketMessage,
});
return ( return (
<> <>
{ socketAlert && <SocketAlert mb={ 6 }/> }
<AddressCoinBalanceChart/> <AddressCoinBalanceChart/>
<AddressCoinBalanceHistory addressQuery={ addressQuery }/> <AddressCoinBalanceHistory query={ coinBalanceQuery }/>
</> </>
); );
}; };
......
...@@ -2,13 +2,12 @@ import { Box, Hide, Show, Table, Tbody, Th, Tr } from '@chakra-ui/react'; ...@@ -2,13 +2,12 @@ import { Box, Hide, Show, Table, Tbody, Th, Tr } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { Address } from 'types/api/address'; import type { AddressCoinBalanceHistoryResponse } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable'; import SkeletonTable from 'ui/shared/SkeletonTable';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
...@@ -18,17 +17,12 @@ import AddressCoinBalanceSkeletonMobile from './AddressCoinBalanceSkeletonMobile ...@@ -18,17 +17,12 @@ import AddressCoinBalanceSkeletonMobile from './AddressCoinBalanceSkeletonMobile
import AddressCoinBalanceTableItem from './AddressCoinBalanceTableItem'; import AddressCoinBalanceTableItem from './AddressCoinBalanceTableItem';
interface Props { interface Props {
addressQuery: UseQueryResult<Address>; query: UseQueryResult<AddressCoinBalanceHistoryResponse> & {
pagination: PaginationProps;
};
} }
const AddressCoinBalanceHistory = ({ addressQuery }: Props) => { const AddressCoinBalanceHistory = ({ query }: Props) => {
const query = useQueryWithPages({
apiPath: `/node-api/addresses/${ addressQuery.data?.hash }/coin-balance-history`,
queryName: QueryKeys.addressCoinBalanceHistory,
options: {
enabled: Boolean(addressQuery.data),
},
});
const isPaginatorHidden = !query.isLoading && !query.isError && query.pagination.page === 1 && !query.pagination.hasNextPage; const isPaginatorHidden = !query.isLoading && !query.isError && query.pagination.page === 1 && !query.pagination.hasNextPage;
......
...@@ -22,7 +22,7 @@ const AddressCoinBalanceListItem = (props: Props) => { ...@@ -22,7 +22,7 @@ const AddressCoinBalanceListItem = (props: Props) => {
return ( return (
<AccountListItemMobile fontSize="sm" rowGap={ 2 }> <AccountListItemMobile fontSize="sm" rowGap={ 2 }>
<Flex justifyContent="space-between" w="100%"> <Flex justifyContent="space-between" w="100%">
<Text fontWeight={ 600 }>{ BigNumber(props.value).div(WEI).toFormat() } { appConfig.network.currency.symbol }</Text> <Text fontWeight={ 600 }>{ BigNumber(props.value).div(WEI).toFixed(8) } { appConfig.network.currency.symbol }</Text>
<Stat flexGrow="0"> <Stat flexGrow="0">
<StatHelpText display="flex" mb={ 0 } alignItems="center"> <StatHelpText display="flex" mb={ 0 } alignItems="center">
<StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' }/> <StatArrow type={ isPositiveDelta ? 'increase' : 'decrease' }/>
......
...@@ -36,7 +36,7 @@ const AddressCoinBalanceTableItem = (props: Props) => { ...@@ -36,7 +36,7 @@ const AddressCoinBalanceTableItem = (props: Props) => {
<Text variant="secondary">{ dayjs(props.block_timestamp).fromNow() }</Text> <Text variant="secondary">{ dayjs(props.block_timestamp).fromNow() }</Text>
</Td> </Td>
<Td isNumeric pr={ 1 }> <Td isNumeric pr={ 1 }>
<Text>{ BigNumber(props.value).div(WEI).toFormat() }</Text> <Text>{ BigNumber(props.value).div(WEI).toFixed(8) }</Text>
</Td> </Td>
<Td isNumeric display="flex" justifyContent="end"> <Td isNumeric display="flex" justifyContent="end">
<Stat flexGrow="0"> <Stat flexGrow="0">
......
...@@ -2,6 +2,7 @@ import { Flex } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Flex } from '@chakra-ui/react';
import { test as base, expect, devices } from '@playwright/experimental-ct-react'; import { test as base, expect, devices } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
import * as coinBalanceMock from 'mocks/address/coinBalanceHistory';
import * as tokenBalanceMock from 'mocks/address/tokenBalance'; import * as tokenBalanceMock from 'mocks/address/tokenBalance';
import * as socketServer from 'playwright/fixtures/socketServer'; import * as socketServer from 'playwright/fixtures/socketServer';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
...@@ -184,9 +185,7 @@ test.describe('socket', () => { ...@@ -184,9 +185,7 @@ test.describe('socket', () => {
const socket = await createSocket(); const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'addresses:1'); const channel = await socketServer.joinChannel(socket, 'addresses:1');
socketServer.sendMessage(socket, channel, 'coin_balance', { socketServer.sendMessage(socket, channel, 'coin_balance', {
coin_balance: { coin_balance: coinBalanceMock.base,
block_number: 1,
},
}); });
const button = page.getByRole('button', { name: /select/i }); const button = page.getByRole('button', { name: /select/i });
......
import { Text, Alert, Link, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
className?: string;
}
const SocketAlert = ({ className }: Props) => {
return (
<Alert status="warning" className={ className }>
<Text whiteSpace="pre">Connection lost, click </Text>
<Link href={ window.document.location.href }>to load newer records</Link>
</Alert>
);
};
export default chakra(SocketAlert);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment