kopia lustrzana https://github.com/bugout-dev/moonstream
Got existing frontend working with updated API
Currently only renders `"ethereum_blockchain"` events. Moved all stream state into the `useStream` hook. This is how we started and really this is what makes sense rather than putting some pretty heavy logic into the component/view.pull/105/head
rodzic
6959b78daf
commit
686bfef49b
|
@ -44,7 +44,7 @@ const FILTER_TYPES = {
|
||||||
ADDRESS: 0,
|
ADDRESS: 0,
|
||||||
GAS: 1,
|
GAS: 1,
|
||||||
GAS_PRICE: 2,
|
GAS_PRICE: 2,
|
||||||
AMMOUNT: 3,
|
AMOUNT: 3,
|
||||||
HASH: 4,
|
HASH: 4,
|
||||||
DISABLED: 99,
|
DISABLED: 99,
|
||||||
};
|
};
|
||||||
|
@ -63,6 +63,7 @@ const EntriesNavigation = () => {
|
||||||
const ui = useContext(UIContext);
|
const ui = useContext(UIContext);
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const { subscriptionsCache } = useSubscriptions();
|
const { subscriptionsCache } = useSubscriptions();
|
||||||
|
const [entries, setEntries] = useState([]);
|
||||||
const [newFilterState, setNewFilterState] = useState([
|
const [newFilterState, setNewFilterState] = useState([
|
||||||
{
|
{
|
||||||
type: FILTER_TYPES.ADDRESS,
|
type: FILTER_TYPES.ADDRESS,
|
||||||
|
@ -75,93 +76,44 @@ const EntriesNavigation = () => {
|
||||||
|
|
||||||
const loadMoreButtonRef = useRef(null);
|
const loadMoreButtonRef = useRef(null);
|
||||||
|
|
||||||
const [streamBoundary, setStreamBoundary] = useState({
|
const {
|
||||||
start_time: null,
|
events,
|
||||||
end_time: null,
|
eventsIsLoading,
|
||||||
include_start: false,
|
eventsRefetch,
|
||||||
include_end: true,
|
eventsIsFetching,
|
||||||
next_event_time: null,
|
eventsRemove,
|
||||||
previous_event_time: null,
|
latestEvents,
|
||||||
});
|
latestEventsIsLoading,
|
||||||
|
latestEventsRefetch,
|
||||||
const updateStreamBoundaryWith = (pageBoundary) => {
|
latestEventsIsFetching,
|
||||||
if (!pageBoundary) {
|
nextEvent,
|
||||||
return streamBoundary;
|
nextEventRefetch,
|
||||||
}
|
previousEvent,
|
||||||
|
previousEventRefetch,
|
||||||
let newBoundary = { ...streamBoundary };
|
streamBoundary,
|
||||||
// We do not check if there is no overlap between the streamBoundary and the pageBoundary - we assume
|
setDefaultBoundary,
|
||||||
// that there *is* an overlap and even if there isn't the stream should gracefully respect the
|
} = useStream(ui.searchTerm.q);
|
||||||
// pageBoundary because that was the most recent request the user made.
|
|
||||||
// TODO(zomglings): If there is no overlap in boundaries, replace streamBoundary with pageBoundary.
|
|
||||||
// No overlap logic:
|
|
||||||
// if (<no overlap>) {
|
|
||||||
// setStreamBoundary(pageBoundary)
|
|
||||||
// return pageBoundary
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (
|
|
||||||
!newBoundary.start_time ||
|
|
||||||
(pageBoundary.start_time &&
|
|
||||||
pageBoundary.start_time <= newBoundary.start_time)
|
|
||||||
) {
|
|
||||||
newBoundary.start_time = pageBoundary.start_time;
|
|
||||||
newBoundary.include_start =
|
|
||||||
newBoundary.include_start || pageBoundary.include_start;
|
|
||||||
}
|
|
||||||
newBoundary.include_start =
|
|
||||||
newBoundary.include_start || pageBoundary.include_start;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!newBoundary.end_time ||
|
|
||||||
(pageBoundary.end_time && pageBoundary.end_time >= newBoundary.end_time)
|
|
||||||
) {
|
|
||||||
newBoundary.end_time = pageBoundary.end_time;
|
|
||||||
newBoundary.include_end =
|
|
||||||
newBoundary.include_end || pageBoundary.include_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
newBoundary.include_end =
|
|
||||||
newBoundary.include_end || pageBoundary.include_end;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!newBoundary.next_event_time ||
|
|
||||||
!pageBoundary.next_event_time ||
|
|
||||||
(pageBoundary.next_event_time &&
|
|
||||||
pageBoundary.next_event_time > newBoundary.next_event_time)
|
|
||||||
) {
|
|
||||||
newBoundary.next_event_time = pageBoundary.next_event_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!newBoundary.previous_event_time ||
|
|
||||||
!pageBoundary.previous_event_time ||
|
|
||||||
(pageBoundary.previous_event_time &&
|
|
||||||
pageBoundary.previous_event_time < newBoundary.previous_event_time)
|
|
||||||
) {
|
|
||||||
newBoundary.previous_event_time = pageBoundary.previous_event_time;
|
|
||||||
}
|
|
||||||
setStreamBoundary(newBoundary);
|
|
||||||
return newBoundary;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { EntriesPages, isLoading, refetch, isFetching, remove } = useStream({
|
|
||||||
searchQuery: ui.searchTerm,
|
|
||||||
start_time: streamBoundary.start_time,
|
|
||||||
end_time: streamBoundary.end_time,
|
|
||||||
include_start: streamBoundary.include_start,
|
|
||||||
include_end: streamBoundary.include_end,
|
|
||||||
updateStreamBoundaryWith: updateStreamBoundaryWith,
|
|
||||||
streamBoundary: streamBoundary,
|
|
||||||
setStreamBoundary: setStreamBoundary,
|
|
||||||
isContent: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!streamBoundary.start_time && !streamBoundary.end_time) {
|
if (!streamBoundary.start_time && !streamBoundary.end_time) {
|
||||||
refetch();
|
setDefaultBoundary();
|
||||||
|
} else {
|
||||||
|
eventsRefetch();
|
||||||
|
latestEventsRefetch();
|
||||||
|
nextEventRefetch();
|
||||||
|
previousEventRefetch();
|
||||||
}
|
}
|
||||||
}, [streamBoundary, refetch]);
|
}, [streamBoundary]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (events) {
|
||||||
|
events.then((data) => {
|
||||||
|
if (data && data.events) {
|
||||||
|
setEntries(data.events);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [events]);
|
||||||
|
|
||||||
const setFilterProps = useCallback(
|
const setFilterProps = useCallback(
|
||||||
(filterIdx, props) => {
|
(filterIdx, props) => {
|
||||||
|
@ -183,13 +135,6 @@ const EntriesNavigation = () => {
|
||||||
}
|
}
|
||||||
}, [subscriptionsCache, newFilterState, setFilterProps]);
|
}, [subscriptionsCache, newFilterState, setFilterProps]);
|
||||||
|
|
||||||
const entriesPagesData = EntriesPages
|
|
||||||
? EntriesPages.data.map((page) => {
|
|
||||||
return page;
|
|
||||||
})
|
|
||||||
: [""];
|
|
||||||
|
|
||||||
const entries = entriesPagesData.flat();
|
|
||||||
const canCreate = false;
|
const canCreate = false;
|
||||||
|
|
||||||
const canDelete = false;
|
const canDelete = false;
|
||||||
|
@ -265,7 +210,7 @@ const EntriesNavigation = () => {
|
||||||
direction="column"
|
direction="column"
|
||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
>
|
>
|
||||||
{entries && !isLoading ? (
|
{entries && !eventsIsLoading ? (
|
||||||
<>
|
<>
|
||||||
<Drawer onClose={onClose} isOpen={isOpen} size="lg">
|
<Drawer onClose={onClose} isOpen={isOpen} size="lg">
|
||||||
<DrawerOverlay />
|
<DrawerOverlay />
|
||||||
|
@ -476,10 +421,10 @@ const EntriesNavigation = () => {
|
||||||
//onScroll={(e) => handleScroll(e)}
|
//onScroll={(e) => handleScroll(e)}
|
||||||
>
|
>
|
||||||
<Stack direction="row" justifyContent="space-between">
|
<Stack direction="row" justifyContent="space-between">
|
||||||
{!isFetching ? (
|
{!eventsIsFetching ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove();
|
eventsRemove();
|
||||||
setStreamBoundary({
|
setStreamBoundary({
|
||||||
start_time: null,
|
start_time: null,
|
||||||
end_time: null,
|
end_time: null,
|
||||||
|
@ -505,7 +450,7 @@ const EntriesNavigation = () => {
|
||||||
|
|
||||||
{streamBoundary.next_event_time &&
|
{streamBoundary.next_event_time &&
|
||||||
streamBoundary.end_time != 0 &&
|
streamBoundary.end_time != 0 &&
|
||||||
!isFetching ? (
|
!eventsIsFetching ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateStreamBoundaryWith({
|
updateStreamBoundaryWith({
|
||||||
|
@ -523,24 +468,22 @@ const EntriesNavigation = () => {
|
||||||
"" // some strange behaivior without else condition return 0 wich can see on frontend page
|
"" // some strange behaivior without else condition return 0 wich can see on frontend page
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{entries
|
{entries.map((entry, idx) => (
|
||||||
?.sort((a, b) => b.timestamp - a.timestamp) // TODO(Andrey) improve that for bi chunks of data sorting can take time
|
<StreamEntry
|
||||||
.map((entry, idx) => (
|
showOnboardingTooltips={false}
|
||||||
<StreamEntry
|
key={`entry-list-${idx}`}
|
||||||
showOnboardingTooltips={false}
|
entry={entry}
|
||||||
key={`entry-list-${idx}`}
|
disableDelete={!canDelete}
|
||||||
entry={entry}
|
disableCopy={!canCreate}
|
||||||
disableDelete={!canDelete}
|
filterCallback={handleFilterStateCallback}
|
||||||
disableCopy={!canCreate}
|
filterConstants={{ DIRECTIONS, CONDITION, FILTER_TYPES }}
|
||||||
filterCallback={handleFilterStateCallback}
|
/>
|
||||||
filterConstants={{ DIRECTIONS, CONDITION, FILTER_TYPES }}
|
))}
|
||||||
/>
|
{streamBoundary.previous_event_time && !eventsIsFetching ? (
|
||||||
))}
|
|
||||||
{streamBoundary.previous_event_time && !isFetching ? (
|
|
||||||
<Center>
|
<Center>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove();
|
eventsRemove();
|
||||||
updateStreamBoundaryWith({
|
updateStreamBoundaryWith({
|
||||||
start_time: streamBoundary.previous_event_time - 5 * 60,
|
start_time: streamBoundary.previous_event_time - 5 * 60,
|
||||||
include_start: false,
|
include_start: false,
|
||||||
|
@ -555,7 +498,7 @@ const EntriesNavigation = () => {
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<Center>
|
<Center>
|
||||||
{!isFetching ? (
|
{!eventsIsFetching ? (
|
||||||
"Тransactions not found. You can subscribe to more addresses in Subscriptions menu."
|
"Тransactions not found. You can subscribe to more addresses in Subscriptions menu."
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
@ -567,7 +510,7 @@ const EntriesNavigation = () => {
|
||||||
)}
|
)}
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
{streamBoundary.previous_event_time && isLoading ? (
|
{streamBoundary.previous_event_time && eventsIsLoading ? (
|
||||||
<Center>
|
<Center>
|
||||||
<Spinner
|
<Spinner
|
||||||
//hidden={!isFetchingMore}
|
//hidden={!isFetchingMore}
|
||||||
|
|
|
@ -34,7 +34,7 @@ const StreamEntry_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
h="100%"
|
h="100%"
|
||||||
spacing={0}
|
spacing={0}
|
||||||
>
|
>
|
||||||
{entry.subscription_type_id === "0" && (
|
{entry.event_type === "ethereum_blockchain" && (
|
||||||
<EthereumMempoolCard entry={entry} />
|
<EthereumMempoolCard entry={entry} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
const { onCopy, hasCopied } = useClipboard(copyString, 1);
|
const { onCopy, hasCopied } = useClipboard(copyString, 1);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
console.log("GREEN RAIN:", subscriptionsCache.data);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasCopied && copyString) {
|
if (hasCopied && copyString) {
|
||||||
toast("Copied to clipboard", "success");
|
toast("Copied to clipboard", "success");
|
||||||
|
@ -38,14 +40,24 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
|
|
||||||
const from_color =
|
const from_color =
|
||||||
subscriptionsCache.data.subscriptions.find((obj) => {
|
subscriptionsCache.data.subscriptions.find((obj) => {
|
||||||
return obj.address === entry.from_address;
|
return obj.address === entry.event_data.from;
|
||||||
})?.color ?? "gray.500";
|
})?.color ?? "gray.500";
|
||||||
|
|
||||||
|
const from_label =
|
||||||
|
subscriptionsCache.data.subscriptions.find((obj) => {
|
||||||
|
return obj.address === entry.event_data.from;
|
||||||
|
})?.label ?? entry.event_data.from;
|
||||||
|
|
||||||
const to_color =
|
const to_color =
|
||||||
subscriptionsCache.data.subscriptions.find((obj) => {
|
subscriptionsCache.data.subscriptions.find((obj) => {
|
||||||
return obj.address === entry.to_address;
|
return obj.address === entry.event_data.to;
|
||||||
})?.color ?? "gray.500";
|
})?.color ?? "gray.500";
|
||||||
|
|
||||||
|
const to_label =
|
||||||
|
subscriptionsCache.data.subscriptions.find((obj) => {
|
||||||
|
return obj.address === entry.event_data.to;
|
||||||
|
})?.label ?? entry.event_data.to;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className={className}>
|
<Stack className={className}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -76,8 +88,12 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
Hash
|
Hash
|
||||||
</Heading>
|
</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Text isTruncated onClick={() => setCopyString(entry.hash)} pr={12}>
|
<Text
|
||||||
{entry.hash}
|
isTruncated
|
||||||
|
onClick={() => setCopyString(entry.event_data.hash)}
|
||||||
|
pr={12}
|
||||||
|
>
|
||||||
|
{entry.event_data.hash}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -125,7 +141,7 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
From:
|
From:
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={entry.from_address} aria-label="From:">
|
<Tooltip label={entry.event_data.from} aria-label="From:">
|
||||||
<Text
|
<Text
|
||||||
mx={0}
|
mx={0}
|
||||||
py="2px"
|
py="2px"
|
||||||
|
@ -134,9 +150,9 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
isTruncated
|
isTruncated
|
||||||
w="calc(100%)"
|
w="calc(100%)"
|
||||||
h="100%"
|
h="100%"
|
||||||
onClick={() => setCopyString(entry.from_address)}
|
onClick={() => setCopyString(entry.event_data.from)}
|
||||||
>
|
>
|
||||||
{entry.from_label ?? entry.from_address}
|
{from_label}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -162,15 +178,15 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
>
|
>
|
||||||
To:
|
To:
|
||||||
</Text>
|
</Text>
|
||||||
<Tooltip label={entry.to_address} aria-label="From:">
|
<Tooltip label={entry.event_data.to} aria-label="From:">
|
||||||
<Text
|
<Text
|
||||||
bgColor={to_color}
|
bgColor={to_color}
|
||||||
isTruncated
|
isTruncated
|
||||||
w="calc(100%)"
|
w="calc(100%)"
|
||||||
h="100%"
|
h="100%"
|
||||||
onClick={() => setCopyString(entry.to_address)}
|
onClick={() => setCopyString(entry.event_data.to)}
|
||||||
>
|
>
|
||||||
{entry.to_label ?? entry.to_address}
|
{to_label}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -188,16 +204,16 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
>
|
>
|
||||||
Gas Price:
|
Gas Price:
|
||||||
</Text>
|
</Text>
|
||||||
<Tooltip label={entry.gasPrice} aria-label="Gas Price:">
|
<Tooltip label={entry.event_data.gasPrice} aria-label="Gas Price:">
|
||||||
<Text
|
<Text
|
||||||
mx={0}
|
mx={0}
|
||||||
py="2px"
|
py="2px"
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
w="calc(100%)"
|
w="calc(100%)"
|
||||||
h="100%"
|
h="100%"
|
||||||
onClick={() => setCopyString(entry.gasPrice)}
|
onClick={() => setCopyString(entry.event_data.gasPrice)}
|
||||||
>
|
>
|
||||||
{entry.gasPrice}
|
{entry.event_data.gasPrice}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -212,16 +228,16 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
>
|
>
|
||||||
Gas:
|
Gas:
|
||||||
</Text>
|
</Text>
|
||||||
<Tooltip label={entry.gas} aria-label="Gas:">
|
<Tooltip label={entry.event_data.gas} aria-label="Gas:">
|
||||||
<Text
|
<Text
|
||||||
mx={0}
|
mx={0}
|
||||||
py="2px"
|
py="2px"
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
w="calc(100%)"
|
w="calc(100%)"
|
||||||
h="100%"
|
h="100%"
|
||||||
onClick={() => setCopyString(entry.gas)}
|
onClick={() => setCopyString(entry.event_data.gas)}
|
||||||
>
|
>
|
||||||
{entry.gas}
|
{entry.event_data.gas}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -236,16 +252,16 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
>
|
>
|
||||||
Value:
|
Value:
|
||||||
</Text>
|
</Text>
|
||||||
<Tooltip label={entry.value} aria-label="Value:">
|
<Tooltip label={entry.event_data.value} aria-label="Value:">
|
||||||
<Text
|
<Text
|
||||||
mx={0}
|
mx={0}
|
||||||
py="2px"
|
py="2px"
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
w="calc(100%)"
|
w="calc(100%)"
|
||||||
h="100%"
|
h="100%"
|
||||||
onClick={() => setCopyString(entry.value)}
|
onClick={() => setCopyString(entry.event_data.value)}
|
||||||
>
|
>
|
||||||
{entry.value}
|
{entry.event_data.value}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -261,20 +277,20 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
>
|
>
|
||||||
Nonce:
|
Nonce:
|
||||||
</Text>
|
</Text>
|
||||||
<Tooltip label={entry.value} aria-label="Value:">
|
<Tooltip label={entry.event_data.value} aria-label="Value:">
|
||||||
<Text
|
<Text
|
||||||
mx={0}
|
mx={0}
|
||||||
py="2px"
|
py="2px"
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
w="calc(100%)"
|
w="calc(100%)"
|
||||||
h="100%"
|
h="100%"
|
||||||
onClick={() => setCopyString(entry.value)}
|
onClick={() => setCopyString(entry.event_data.value)}
|
||||||
>
|
>
|
||||||
{entry.nonce}
|
{entry.event_data.nonce}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
{entry.timestamp && (
|
{entry.event_timestamp && (
|
||||||
<Flex h="auto" minW="fit-content">
|
<Flex h="auto" minW="fit-content">
|
||||||
<Text
|
<Text
|
||||||
px={1}
|
px={1}
|
||||||
|
@ -285,7 +301,9 @@ const EthereumMempoolCard_ = ({ entry, showOnboardingTooltips, className }) => {
|
||||||
h="100%"
|
h="100%"
|
||||||
borderColor="gray.700"
|
borderColor="gray.700"
|
||||||
>
|
>
|
||||||
{moment(entry.timestamp * 1000).format("DD MMM, YYYY, HH:mm:ss")}{" "}
|
{moment(entry.event_timestamp * 1000).format(
|
||||||
|
"DD MMM, YYYY, HH:mm:ss"
|
||||||
|
)}{" "}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const BUGOUT_API_URL = process.env.NEXT_PUBLIC_SIMIOTICS_SEARCH_URL;
|
export const MOONSTREAM_API_URL = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;
|
||||||
|
|
||||||
export const BUGOUT_ENDPOINTS = {
|
export const BUGOUT_ENDPOINTS = {
|
||||||
Usage: "usage",
|
Usage: "usage",
|
||||||
|
|
|
@ -1,61 +1,211 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
import { StreamService } from "../services";
|
import { StreamService } from "../services";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { queryCacheProps } from "./hookCommon";
|
import { queryCacheProps } from "./hookCommon";
|
||||||
|
import { defaultStreamBoundary } from "../services/servertime.service.js";
|
||||||
|
|
||||||
const useJournalEntries = ({
|
const useStream = (q) => {
|
||||||
searchQuery,
|
const [streamQuery, setStreamQuery] = useState(q || "");
|
||||||
start_time,
|
const [streamBoundary, setStreamBoundary] = useState({});
|
||||||
end_time,
|
|
||||||
include_start,
|
|
||||||
include_end,
|
|
||||||
updateStreamBoundaryWith,
|
|
||||||
}) => {
|
|
||||||
// set our get method
|
|
||||||
const getStream =
|
|
||||||
(searchTerm, start_time, end_time, include_start, include_end) =>
|
|
||||||
async () => {
|
|
||||||
// Request with params to streams
|
|
||||||
const response = await StreamService.getStream({
|
|
||||||
searchTerm: searchTerm,
|
|
||||||
start_time: start_time,
|
|
||||||
end_time: end_time,
|
|
||||||
include_start: include_start,
|
|
||||||
include_end: include_end,
|
|
||||||
});
|
|
||||||
|
|
||||||
// new events from stream
|
const isStreamBoundaryEmpty = () => {
|
||||||
const newEventsList = response.data.stream.map((event) => ({
|
return !streamBoundary.start_time && !streamBoundary.end_time;
|
||||||
...event,
|
};
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
const setDefaultBoundary = async () => {
|
||||||
data: [...newEventsList],
|
const defaultBoundary = await defaultStreamBoundary();
|
||||||
boundaries: { ...response.data.boundaries, update: false },
|
setStreamBoundary(defaultBoundary);
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const { data, isLoading, refetch, isFetching, remove } = useQuery(
|
const updateStreamBoundaryWith = (extensionBoundary) => {
|
||||||
["stream", searchQuery, start_time, end_time],
|
if (!extensionBoundary) {
|
||||||
getStream(searchQuery, start_time, end_time, include_start, include_end),
|
return streamBoundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newBoundary = { ...streamBoundary };
|
||||||
|
// We do not check if there is no overlap between the streamBoundary and the pageBoundary - we assume
|
||||||
|
// that there *is* an overlap and even if there isn't the stream should gracefully respect the
|
||||||
|
// pageBoundary because that was the most recent request the user made.
|
||||||
|
// TODO(zomglings): If there is no overlap in boundaries, replace streamBoundary with pageBoundary.
|
||||||
|
// No overlap logic:
|
||||||
|
// if (<no overlap>) {
|
||||||
|
// setStreamBoundary(pageBoundary)
|
||||||
|
// return pageBoundary
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (
|
||||||
|
!newBoundary.start_time ||
|
||||||
|
(extensionBoundary.start_time &&
|
||||||
|
extensionBoundary.start_time <= newBoundary.start_time)
|
||||||
|
) {
|
||||||
|
newBoundary.start_time = extensionBoundary.start_time;
|
||||||
|
newBoundary.include_start =
|
||||||
|
newBoundary.include_start || extensionBoundary.include_start;
|
||||||
|
}
|
||||||
|
newBoundary.include_start =
|
||||||
|
newBoundary.include_start || extensionBoundary.include_start;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!newBoundary.end_time ||
|
||||||
|
(extensionBoundary.end_time &&
|
||||||
|
extensionBoundary.end_time >= newBoundary.end_time)
|
||||||
|
) {
|
||||||
|
newBoundary.end_time = extensionBoundary.end_time;
|
||||||
|
newBoundary.include_end =
|
||||||
|
newBoundary.include_end || extensionBoundary.include_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
newBoundary.include_end =
|
||||||
|
newBoundary.include_end || extensionBoundary.include_end;
|
||||||
|
|
||||||
|
setStreamBoundary(newBoundary);
|
||||||
|
return newBoundary;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEvents = () => async () => {
|
||||||
|
const response = await StreamService.getEvents({
|
||||||
|
streamQuery,
|
||||||
|
...streamBoundary,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: events,
|
||||||
|
isLoading: eventsIsLoading,
|
||||||
|
refetch: eventsRefetch,
|
||||||
|
isFetching: eventsIsFetching,
|
||||||
|
remove: eventsRemove,
|
||||||
|
} = useQuery(
|
||||||
|
["stream-events", streamQuery],
|
||||||
|
() => {
|
||||||
|
if (isStreamBoundaryEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getEvents();
|
||||||
|
},
|
||||||
{
|
{
|
||||||
//refetchInterval: refreshRate,
|
|
||||||
...queryCacheProps,
|
...queryCacheProps,
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
retry: 3,
|
retry: 2,
|
||||||
onSuccess: (response) => {
|
onSuccess: (newEvents) => {
|
||||||
// response is object which return condition in getStream
|
if (newEvents) {
|
||||||
// TODO(andrey): Response should send page parameters inside "boundary" object (can be null).
|
updateStreamBoundaryWith(newEvents.stream_boundary);
|
||||||
updateStreamBoundaryWith(response.boundaries);
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getLatestEvents = async () => {
|
||||||
|
const response = await StreamService.latestEvents({ q: streamQuery });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: latestEvents,
|
||||||
|
isLoading: latestEventsIsLoading,
|
||||||
|
refetch: latestEventsRefetch,
|
||||||
|
isFetching: latestEventsIsFetching,
|
||||||
|
remove: latestEventsRemove,
|
||||||
|
} = useQuery(
|
||||||
|
["stream-latest", streamQuery],
|
||||||
|
() => {
|
||||||
|
if (isStreamBoundaryEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getLatestEvents();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...queryCacheProps,
|
||||||
|
keepPreviousData: false,
|
||||||
|
retry: 2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const getNextEvent = async () => {
|
||||||
|
const response = await StreamService.nextEvent({
|
||||||
|
q: streamQuery,
|
||||||
|
...streamBoundary,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: nextEvent,
|
||||||
|
isLoading: nextEventIsLoading,
|
||||||
|
refetch: nextEventRefetch,
|
||||||
|
isFetching: nextEventIsFetching,
|
||||||
|
remove: nextEventRemove,
|
||||||
|
} = useQuery(
|
||||||
|
["stream-next", streamQuery],
|
||||||
|
() => {
|
||||||
|
if (isStreamBoundaryEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getNextEvent();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...queryCacheProps,
|
||||||
|
keepPreviousData: false,
|
||||||
|
retry: 2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const getPreviousEvent = async () => {
|
||||||
|
const response = await StreamService.previousEvent({
|
||||||
|
q: streamQuery,
|
||||||
|
...streamBoundary,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: previousEvent,
|
||||||
|
isLoading: previousEventIsLoading,
|
||||||
|
refetch: previousEventRefetch,
|
||||||
|
isFetching: previousEventIsFetching,
|
||||||
|
remove: previousEventRemove,
|
||||||
|
} = useQuery(
|
||||||
|
["stream-previous", streamQuery],
|
||||||
|
() => {
|
||||||
|
if (isStreamBoundaryEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getPreviousEvent();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...queryCacheProps,
|
||||||
|
keepPreviousData: false,
|
||||||
|
retry: 2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
EntriesPages: data,
|
streamBoundary,
|
||||||
isLoading,
|
setDefaultBoundary,
|
||||||
refetch,
|
updateStreamBoundaryWith,
|
||||||
isFetching,
|
events,
|
||||||
remove,
|
eventsIsLoading,
|
||||||
|
eventsRefetch,
|
||||||
|
eventsIsFetching,
|
||||||
|
eventsRemove,
|
||||||
|
setStreamQuery,
|
||||||
|
latestEvents,
|
||||||
|
latestEventsIsLoading,
|
||||||
|
latestEventsRefetch,
|
||||||
|
latestEventsIsFetching,
|
||||||
|
latestEventsRemove,
|
||||||
|
nextEvent,
|
||||||
|
nextEventIsLoading,
|
||||||
|
nextEventRefetch,
|
||||||
|
nextEventIsFetching,
|
||||||
|
nextEventRemove,
|
||||||
|
previousEvent,
|
||||||
|
previousEventIsLoading,
|
||||||
|
previousEventRefetch,
|
||||||
|
previousEventIsFetching,
|
||||||
|
previousEventRemove,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export default useJournalEntries;
|
export default useStream;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import * as SearchService from "./search.service";
|
|
||||||
import * as AuthService from "./auth.service";
|
import * as AuthService from "./auth.service";
|
||||||
import * as JournalService from "./journal.service";
|
import * as JournalService from "./journal.service";
|
||||||
import * as EntryService from "./entry.service";
|
import * as EntryService from "./entry.service";
|
||||||
|
@ -11,7 +10,6 @@ import * as SubscriptionsService from "./subscriptions.service";
|
||||||
import * as StreamService from "./stream.service";
|
import * as StreamService from "./stream.service";
|
||||||
import * as TxInfoService from "./txinfo.service";
|
import * as TxInfoService from "./txinfo.service";
|
||||||
export {
|
export {
|
||||||
SearchService,
|
|
||||||
AuthService,
|
AuthService,
|
||||||
JournalService,
|
JournalService,
|
||||||
EntryService,
|
EntryService,
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { http } from "../utils";
|
||||||
|
|
||||||
|
import { MOONSTREAM_API_URL } from "../constants";
|
||||||
|
|
||||||
|
export const serverTimeNow = async () => {
|
||||||
|
const response = await http({
|
||||||
|
method: "GET",
|
||||||
|
url: `${MOONSTREAM_API_URL}/now`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timestamp = response.data.epoch_time;
|
||||||
|
// Javascript Date objects are loaded from Unix timestamps at the level of precision of milliseconds
|
||||||
|
// since epoch start. The server returns microseconds since epoch, but the integer part of the response
|
||||||
|
// time is at second-level precision.
|
||||||
|
const jsTimestamp = Math.floor(timestamp * 1000);
|
||||||
|
const jsDate = new Date(jsTimestamp);
|
||||||
|
return jsDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the milliseconds of difference between the clocks of the client and the Moonstream API
|
||||||
|
// server. Since this involves a request to the server, it also includes the latency of making an HTTP
|
||||||
|
// request to the server's /now endpoint and getting a response.
|
||||||
|
export const clientServerOffsetMillis = async () => {
|
||||||
|
// TODO(zomglings): Time the request and use a simple estimate for the latency based on:
|
||||||
|
// 1. Size of request
|
||||||
|
// 2. Size of resposne
|
||||||
|
// 3. Profiling the body of the `/now` handler on the server.
|
||||||
|
// At least a naive estimate would be something like:
|
||||||
|
// const currentServerTime = serverTime + (responseAt - requestAt)*3/4
|
||||||
|
//
|
||||||
|
// This assumes that 3/4 of the latency was involved in sending the response back to the client.
|
||||||
|
//
|
||||||
|
// Unfortunately, it also assumes that the client clock and server clock are moving at the same
|
||||||
|
// speed. Of course, we could check the speeds against each other by repeated calls to this method,
|
||||||
|
// but I think we don't need that level of synchronizating yet.
|
||||||
|
const serverTime = await serverTimeNow();
|
||||||
|
const clientTime = new Date();
|
||||||
|
return clientTime - serverTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a stream boundary representing the past 10 minutes.
|
||||||
|
export const defaultStreamBoundary = async () => {
|
||||||
|
const endTime = await serverTimeNow();
|
||||||
|
// 1 hour ago (in milliseconds)
|
||||||
|
const startTimeMillis = endTime - 60 * 60 * 1000;
|
||||||
|
const streamBoundary = {
|
||||||
|
start_time: Math.floor(startTimeMillis / 1000),
|
||||||
|
end_time: Math.floor(endTime / 1000),
|
||||||
|
include_start: true,
|
||||||
|
include_end: true,
|
||||||
|
};
|
||||||
|
return streamBoundary;
|
||||||
|
};
|
|
@ -3,16 +3,17 @@ import { http } from "../utils";
|
||||||
|
|
||||||
const API = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;
|
const API = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;
|
||||||
|
|
||||||
export const getStream = ({
|
export const getEvents = ({
|
||||||
searchTerm,
|
q,
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
include_start,
|
include_start,
|
||||||
include_end,
|
include_end,
|
||||||
}) => {
|
}) => {
|
||||||
let params = {
|
let params = {};
|
||||||
q: searchTerm,
|
if (q) {
|
||||||
};
|
params.q = q;
|
||||||
|
}
|
||||||
if (start_time || start_time === 0) {
|
if (start_time || start_time === 0) {
|
||||||
params.start_time = start_time;
|
params.start_time = start_time;
|
||||||
}
|
}
|
||||||
|
@ -32,3 +33,75 @@ export const getStream = ({
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// params is expected to be an object defining the query parameters to the /streams/latest endpoint
|
||||||
|
// The /streams/latest endpoint accepts the following query parameters:
|
||||||
|
// - q: Query filters of the form type:<subscription type> or subscription:from:<address> or subscription:to:<address>
|
||||||
|
// This will change over time and the API documentation should be the source of truth for these parameters.
|
||||||
|
// TODO(zomglings): Link here once API docs are set up.
|
||||||
|
export const latestEvents = (params) => {
|
||||||
|
return http({ method: "GET", url: `${API}/streams/latest`, params });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nextEvent = ({
|
||||||
|
q,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
include_start,
|
||||||
|
include_end,
|
||||||
|
}) => {
|
||||||
|
let params = {};
|
||||||
|
if (q) {
|
||||||
|
params.q = q;
|
||||||
|
}
|
||||||
|
if (start_time || start_time === 0) {
|
||||||
|
params.start_time = start_time;
|
||||||
|
}
|
||||||
|
if (end_time || end_time === 0) {
|
||||||
|
params.end_time = end_time;
|
||||||
|
}
|
||||||
|
if (include_start) {
|
||||||
|
params.include_start = include_start;
|
||||||
|
}
|
||||||
|
if (include_end) {
|
||||||
|
params.include_end = include_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return http({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API}/streams/next`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const previousEvent = ({
|
||||||
|
q,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
include_start,
|
||||||
|
include_end,
|
||||||
|
}) => {
|
||||||
|
// TODO(zomglings): Factor this query parameter building code out into a separate function.
|
||||||
|
let params = {};
|
||||||
|
if (q) {
|
||||||
|
params.q = q;
|
||||||
|
}
|
||||||
|
if (start_time || start_time === 0) {
|
||||||
|
params.start_time = start_time;
|
||||||
|
}
|
||||||
|
if (end_time || end_time === 0) {
|
||||||
|
params.end_time = end_time;
|
||||||
|
}
|
||||||
|
if (include_start) {
|
||||||
|
params.include_start = include_start;
|
||||||
|
}
|
||||||
|
if (include_end) {
|
||||||
|
params.include_end = include_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return http({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API}/streams/previous`,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
Ładowanie…
Reference in New Issue