Commit 37c529bf authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #729 from blockscout/new-sockets-2

New sockets
parents 2d8cd224 3a2c71fd
...@@ -3,6 +3,7 @@ import type { Channel } from 'phoenix'; ...@@ -3,6 +3,7 @@ import type { Channel } from 'phoenix';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import type { NewBlockSocketResponse } from 'types/api/block'; import type { NewBlockSocketResponse } from 'types/api/block';
import type { SmartContractVerificationResponse } from 'types/api/contract'; import type { SmartContractVerificationResponse } from 'types/api/contract';
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
...@@ -10,6 +11,7 @@ export type SocketMessageParams = SocketMessage.NewBlock | ...@@ -10,6 +11,7 @@ export type SocketMessageParams = SocketMessage.NewBlock |
SocketMessage.BlocksIndexStatus | SocketMessage.BlocksIndexStatus |
SocketMessage.InternalTxsIndexStatus | SocketMessage.InternalTxsIndexStatus |
SocketMessage.TxStatusUpdate | SocketMessage.TxStatusUpdate |
SocketMessage.TxRawTrace |
SocketMessage.NewTx | SocketMessage.NewTx |
SocketMessage.NewPendingTx | SocketMessage.NewPendingTx |
SocketMessage.AddressBalance | SocketMessage.AddressBalance |
...@@ -19,7 +21,9 @@ SocketMessage.AddressCoinBalance | ...@@ -19,7 +21,9 @@ SocketMessage.AddressCoinBalance |
SocketMessage.AddressTxs | SocketMessage.AddressTxs |
SocketMessage.AddressTxsPending | SocketMessage.AddressTxsPending |
SocketMessage.AddressTokenTransfer | SocketMessage.AddressTokenTransfer |
SocketMessage.AddressChangedBytecode |
SocketMessage.TokenTransfers | SocketMessage.TokenTransfers |
SocketMessage.TokenTotalSupply |
SocketMessage.ContractVerification | SocketMessage.ContractVerification |
SocketMessage.Unknown; SocketMessage.Unknown;
...@@ -35,6 +39,7 @@ export namespace SocketMessage { ...@@ -35,6 +39,7 @@ export namespace SocketMessage {
export type BlocksIndexStatus = SocketMessageParamsGeneric<'block_index_status', {finished: boolean; ratio: string}>; export type BlocksIndexStatus = SocketMessageParamsGeneric<'block_index_status', {finished: boolean; ratio: string}>;
export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'internal_txs_index_status', {finished: boolean; ratio: string}>; export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'internal_txs_index_status', {finished: boolean; ratio: string}>;
export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>; export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>;
export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>;
...@@ -45,7 +50,9 @@ export namespace SocketMessage { ...@@ -45,7 +50,9 @@ export namespace SocketMessage {
export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transaction: Transaction }>; export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transaction: Transaction }>;
export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transaction: Transaction }>; export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transaction: Transaction }>;
export type AddressTokenTransfer = SocketMessageParamsGeneric<'token_transfer', { token_transfer: TokenTransfer }>; export type AddressTokenTransfer = SocketMessageParamsGeneric<'token_transfer', { token_transfer: TokenTransfer }>;
export type AddressChangedBytecode = SocketMessageParamsGeneric<'changed_bytecode', Record<string, never>>;
export type TokenTransfers = SocketMessageParamsGeneric<'token_transfer', {token_transfer: number }>; export type TokenTransfers = SocketMessageParamsGeneric<'token_transfer', {token_transfer: number }>;
export type TokenTotalSupply = SocketMessageParamsGeneric<'total_supply', {total_supply: number }>;
export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>; export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>;
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>; export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
} }
...@@ -61,6 +61,8 @@ export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transacti ...@@ -61,6 +61,8 @@ export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transacti
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;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'new_block', payload: NewBlockSocketResponse): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'new_block', payload: NewBlockSocketResponse): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'verification_result', payload: SmartContractVerificationResponse): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'verification_result', payload: SmartContractVerificationResponse): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'total_supply', payload: { total_supply: number}): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'changed_bytecode', payload: Record<string, never>): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: string, payload: unknown): void { export function sendMessage(socket: WebSocket, channel: Channel, msg: string, payload: unknown): void {
socket.send(JSON.stringify([ socket.send(JSON.stringify([
...channel, ...channel,
......
import { test, expect } from '@playwright/experimental-ct-react'; import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
import * as contractMock from 'mocks/contract/info'; import * as contractMock from 'mocks/contract/info';
import * as socketServer from 'playwright/fixtures/socketServer';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
...@@ -15,6 +16,14 @@ const hooksConfig = { ...@@ -15,6 +16,14 @@ const hooksConfig = {
}, },
}; };
const test = base.extend<socketServer.SocketServerFixture>({
createSocket: socketServer.createSocket,
});
// FIXME
// test cases which use socket cannot run in parallel since the socket server always run on the same port
test.describe.configure({ mode: 'serial' });
test('verified with changed byte code +@mobile +@dark-mode', async({ mount, page }) => { test('verified with changed byte code +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(CONTRACT_API_URL, (route) => route.fulfill({ await page.route(CONTRACT_API_URL, (route) => route.fulfill({
status: 200, status: 200,
...@@ -24,11 +33,32 @@ test('verified with changed byte code +@mobile +@dark-mode', async({ mount, page ...@@ -24,11 +33,32 @@ test('verified with changed byte code +@mobile +@dark-mode', async({ mount, page
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<ContractCode addressHash={ addressHash } noSocket/>
</TestApp>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
test('verified with changed byte code socket', async({ mount, page, createSocket }) => {
await page.route(CONTRACT_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(contractMock.verified),
}));
await page.route('https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/**', (route) => route.abort());
const component = await mount(
<TestApp withSocket>
<ContractCode addressHash={ addressHash }/> <ContractCode addressHash={ addressHash }/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'addresses:' + addressHash.toLowerCase());
socketServer.sendMessage(socket, channel, 'changed_bytecode', {});
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -41,7 +71,7 @@ test('verified with multiple sources +@mobile', async({ mount, page }) => { ...@@ -41,7 +71,7 @@ test('verified with multiple sources +@mobile', async({ mount, page }) => {
await mount( await mount(
<TestApp> <TestApp>
<ContractCode addressHash={ addressHash }/> <ContractCode addressHash={ addressHash } noSocket/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -60,7 +90,7 @@ test('verified via sourcify', async({ mount, page }) => { ...@@ -60,7 +90,7 @@ test('verified via sourcify', async({ mount, page }) => {
await mount( await mount(
<TestApp> <TestApp>
<ContractCode addressHash={ addressHash }/> <ContractCode addressHash={ addressHash } noSocket/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -77,7 +107,7 @@ test('self destructed', async({ mount, page }) => { ...@@ -77,7 +107,7 @@ test('self destructed', async({ mount, page }) => {
await mount( await mount(
<TestApp> <TestApp>
<ContractCode addressHash={ addressHash }/> <ContractCode addressHash={ addressHash } noSocket/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -95,7 +125,7 @@ test('with twin address alert +@mobile', async({ mount, page }) => { ...@@ -95,7 +125,7 @@ test('with twin address alert +@mobile', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<ContractCode addressHash={ addressHash }/> <ContractCode addressHash={ addressHash } noSocket/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -112,7 +142,7 @@ test('with proxy address alert +@mobile', async({ mount, page }) => { ...@@ -112,7 +142,7 @@ test('with proxy address alert +@mobile', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<ContractCode addressHash={ addressHash }/> <ContractCode addressHash={ addressHash } noSocket/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
...@@ -129,7 +159,7 @@ test('non verified', async({ mount, page }) => { ...@@ -129,7 +159,7 @@ test('non verified', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<ContractCode addressHash={ addressHash }/> <ContractCode addressHash={ addressHash } noSocket/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
......
...@@ -2,8 +2,12 @@ import { Flex, Skeleton, Button, Grid, GridItem, Text, Alert, Link, chakra, Box ...@@ -2,8 +2,12 @@ import { Flex, Skeleton, Button, Grid, GridItem, Text, Alert, Link, chakra, Box
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -16,6 +20,8 @@ import ContractSourceCode from './ContractSourceCode'; ...@@ -16,6 +20,8 @@ import ContractSourceCode from './ContractSourceCode';
type Props = { type Props = {
addressHash?: string; addressHash?: string;
// prop for pw tests only
noSocket?: boolean;
} }
const InfoItem = chakra(({ label, value, className }: { label: string; value: string; className?: string }) => ( const InfoItem = chakra(({ label, value, className }: { label: string; value: string; className?: string }) => (
...@@ -25,15 +31,33 @@ const InfoItem = chakra(({ label, value, className }: { label: string; value: st ...@@ -25,15 +31,33 @@ const InfoItem = chakra(({ label, value, className }: { label: string; value: st
</GridItem> </GridItem>
)); ));
const ContractCode = ({ addressHash }: Props) => { const ContractCode = ({ addressHash, noSocket }: Props) => {
const [ isSocketOpen, setIsSocketOpen ] = React.useState(false);
const [ isChangedBytecodeSocket, setIsChangedBytecodeSocket ] = React.useState<boolean>();
const { data, isLoading, isError } = useApiQuery('contract', { const { data, isLoading, isError } = useApiQuery('contract', {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
queryOptions: { queryOptions: {
enabled: Boolean(addressHash), enabled: Boolean(addressHash) && (noSocket || isSocketOpen),
refetchOnMount: false, refetchOnMount: false,
}, },
}); });
const handleChangedBytecodeMessage: SocketMessage.AddressChangedBytecode['handler'] = React.useCallback(() => {
setIsChangedBytecodeSocket(true);
}, [ ]);
const channel = useSocketChannel({
topic: `addresses:${ addressHash?.toLowerCase() }`,
isDisabled: !addressHash,
onJoin: () => setIsSocketOpen(true),
});
useSocketMessage({
channel,
event: 'changed_bytecode',
handler: handleChangedBytecodeMessage,
});
if (isError) { if (isError) {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
...@@ -117,7 +141,7 @@ const ContractCode = ({ addressHash }: Props) => { ...@@ -117,7 +141,7 @@ const ContractCode = ({ addressHash }: Props) => {
{ data.sourcify_repo_url && <LinkExternal href={ data.sourcify_repo_url } fontSize="md">View contract in Sourcify repository</LinkExternal> } { data.sourcify_repo_url && <LinkExternal href={ data.sourcify_repo_url } fontSize="md">View contract in Sourcify repository</LinkExternal> }
</Alert> </Alert>
) } ) }
{ data.is_changed_bytecode && ( { (data.is_changed_bytecode || isChangedBytecodeSocket) && (
<Alert status="warning"> <Alert status="warning">
Warning! Contract bytecode has been changed and does not match the verified one. Therefore, interaction with this smart contract may be risky. Warning! Contract bytecode has been changed and does not match the verified one. Therefore, interaction with this smart contract may be risky.
</Alert> </Alert>
......
import { test, expect } 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 { token as contract } from 'mocks/address/address'; import { token as contract } from 'mocks/address/address';
import { tokenInfo, tokenCounters } from 'mocks/tokens/tokenInfo'; import { tokenInfo, tokenCounters } from 'mocks/tokens/tokenInfo';
import * as socketServer from 'playwright/fixtures/socketServer';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder'; import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder';
...@@ -20,8 +21,15 @@ const hooksConfig = { ...@@ -20,8 +21,15 @@ const hooksConfig = {
}, },
}; };
// FIXME: idk why mobile test doesn't work (it's ok locally) const test = base.extend<socketServer.SocketServerFixture>({
test('base view +@mobile +@dark-mode', async({ mount, page }) => { createSocket: socketServer.createSocket,
});
// FIXME
// test cases which use socket cannot run in parallel since the socket server always run on the same port
test.describe.configure({ mode: 'serial' });
test.beforeEach(async({ page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200, status: 200,
body: '', body: '',
...@@ -43,15 +51,41 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -43,15 +51,41 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => {
status: 200, status: 200,
body: JSON.stringify({}), body: JSON.stringify({}),
})); }));
});
test('base view', async({ mount, page, createSocket }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp withSocket>
<Token/> <Token/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'tokens:1');
socketServer.sendMessage(socket, channel, 'total_supply', { total_supply: 10 ** 20 });
await insertAdPlaceholder(page); await insertAdPlaceholder(page);
await expect(component.locator('main')).toHaveScreenshot(); await expect(component.locator('main')).toHaveScreenshot();
}); });
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ mount, page, createSocket }) => {
const component = await mount(
<TestApp withSocket>
<Token/>
</TestApp>,
{ hooksConfig },
);
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'tokens:1');
socketServer.sendMessage(socket, channel, 'total_supply', { total_supply: 10 ** 20 });
await insertAdPlaceholder(page);
await expect(component.locator('main')).toHaveScreenshot();
});
});
import { Skeleton, Box, Flex, SkeletonCircle, Icon, Tag } from '@chakra-ui/react'; import { Skeleton, Box, Flex, SkeletonCircle, Icon, Tag } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { TokenInfo } from 'types/api/token';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import iconSuccess from 'icons/status/success.svg'; import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useContractTabs from 'lib/hooks/useContractTabs'; import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressContract from 'ui/address/AddressContract'; import AddressContract from 'ui/address/AddressContract';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
...@@ -29,6 +34,8 @@ import TokenTransfer from 'ui/token/TokenTransfer/TokenTransfer'; ...@@ -29,6 +34,8 @@ import TokenTransfer from 'ui/token/TokenTransfer/TokenTransfer';
export type TokenTabs = 'token_transfers' | 'holders' | 'inventory'; export type TokenTabs = 'token_transfers' | 'holders' | 'inventory';
const TokenPageContent = () => { const TokenPageContent = () => {
const [ isSocketOpen, setIsSocketOpen ] = React.useState(false);
const [ totalSupplySocket, setTotalSupplySocket ] = React.useState<number>();
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -40,9 +47,44 @@ const TokenPageContent = () => { ...@@ -40,9 +47,44 @@ const TokenPageContent = () => {
const hashString = router.query.hash?.toString(); const hashString = router.query.hash?.toString();
const queryClient = useQueryClient();
const tokenQuery = useApiQuery('token', { const tokenQuery = useApiQuery('token', {
pathParams: { hash: hashString }, pathParams: { hash: hashString },
queryOptions: { enabled: Boolean(router.query.hash) }, queryOptions: { enabled: isSocketOpen && Boolean(router.query.hash) },
});
React.useEffect(() => {
if (tokenQuery.data && totalSupplySocket) {
queryClient.setQueryData(getResourceKey('token', { pathParams: { hash: hashString } }), (prevData: TokenInfo | undefined) => {
if (prevData) {
return { ...prevData, total_supply: totalSupplySocket.toString() };
}
});
}
}, [ tokenQuery.data, totalSupplySocket, hashString, queryClient ]);
const handleTotalSupplyMessage: SocketMessage.TokenTotalSupply['handler'] = React.useCallback((payload) => {
const prevData = queryClient.getQueryData(getResourceKey('token', { pathParams: { hash: hashString } }));
if (!prevData) {
setTotalSupplySocket(payload.total_supply);
}
queryClient.setQueryData(getResourceKey('token', { pathParams: { hash: hashString } }), (prevData: TokenInfo | undefined) => {
if (prevData) {
return { ...prevData, total_supply: payload.total_supply.toString() };
}
});
}, [ queryClient, hashString ]);
const channel = useSocketChannel({
topic: `tokens:${ hashString?.toLowerCase() }`,
isDisabled: !hashString,
onJoin: () => setIsSocketOpen(true),
});
useSocketMessage({
channel,
event: 'total_supply',
handler: handleTotalSupplyMessage,
}); });
useEffect(() => { useEffect(() => {
......
...@@ -2,9 +2,14 @@ import { Flex, Skeleton } from '@chakra-ui/react'; ...@@ -2,9 +2,14 @@ import { Flex, Skeleton } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { RawTracesResponse } from 'types/api/rawTrace';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { SECOND } from 'lib/consts'; import { SECOND } from 'lib/consts';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import RawDataSnippet from 'ui/shared/RawDataSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet';
import TxPendingAlert from 'ui/tx/TxPendingAlert'; import TxPendingAlert from 'ui/tx/TxPendingAlert';
...@@ -12,6 +17,8 @@ import TxSocketAlert from 'ui/tx/TxSocketAlert'; ...@@ -12,6 +17,8 @@ import TxSocketAlert from 'ui/tx/TxSocketAlert';
import useFetchTxInfo from 'ui/tx/useFetchTxInfo'; import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
const TxRawTrace = () => { const TxRawTrace = () => {
const [ isSocketOpen, setIsSocketOpen ] = React.useState(false);
const [ rawTraces, setRawTraces ] = React.useState<RawTracesResponse>();
const router = useRouter(); const router = useRouter();
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
...@@ -19,10 +26,25 @@ const TxRawTrace = () => { ...@@ -19,10 +26,25 @@ const TxRawTrace = () => {
const { data, isLoading, isError } = useApiQuery('tx_raw_trace', { const { data, isLoading, isError } = useApiQuery('tx_raw_trace', {
pathParams: { hash }, pathParams: { hash },
queryOptions: { queryOptions: {
enabled: Boolean(hash) && Boolean(txInfo.data?.status), enabled: Boolean(hash) && Boolean(txInfo.data?.status) && isSocketOpen,
}, },
}); });
const handleRawTraceMessage: SocketMessage.TxRawTrace['handler'] = React.useCallback((payload) => {
setRawTraces(payload);
}, [ ]);
const channel = useSocketChannel({
topic: `transactions:${ hash }`,
isDisabled: !hash || !txInfo.data?.status,
onJoin: () => setIsSocketOpen(true),
});
useSocketMessage({
channel,
event: 'raw_trace',
handler: handleRawTraceMessage,
});
if (!txInfo.isLoading && !txInfo.isError && !txInfo.data.status) { if (!txInfo.isLoading && !txInfo.isError && !txInfo.data.status) {
return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>; return txInfo.socketStatus ? <TxSocketAlert status={ txInfo.socketStatus }/> : <TxPendingAlert/>;
} }
...@@ -42,11 +64,13 @@ const TxRawTrace = () => { ...@@ -42,11 +64,13 @@ const TxRawTrace = () => {
); );
} }
if (data.length === 0) { const dataToDisplay = rawTraces ? rawTraces : data;
if (dataToDisplay.length === 0) {
return <span>No trace entries found.</span>; return <span>No trace entries found.</span>;
} }
const text = JSON.stringify(data, undefined, 4); const text = JSON.stringify(dataToDisplay, undefined, 4);
return <RawDataSnippet data={ text }/>; return <RawDataSnippet data={ text }/>;
}; };
......
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