Skip to content
This repository has been archived by the owner on Jan 15, 2025. It is now read-only.

Added an example of server selection by latency #1516

Open
wants to merge 7 commits into
base: development
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added finding near hive server and test proposals queries
dkildar committed Nov 26, 2023
commit 26e0edba9a09b3704ea7b23afceddb777697111a
20 changes: 18 additions & 2 deletions src/common/api/hive.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Client, RCAPI, utils } from "@hiveio/dhive";
import { Client, RCAPI } from "@hiveio/dhive";

import { RCAccount } from "@hiveio/dhive/lib/chain/rc";

import { TrendingTag } from "../store/trending-tags/types";
import { DynamicProps } from "../store/dynamic-props/types";
import { FullAccount, AccountProfile, AccountFollowStats } from "../store/accounts/types";
import { AccountFollowStats, AccountProfile, FullAccount } from "../store/accounts/types";
import { Entry } from "../store/entries/types";

import parseAsset from "../helper/parse-asset";
@@ -14,6 +14,7 @@ import isCommunity from "../helper/is-community";
import SERVERS from "../constants/servers.json";
import { dataLimit } from "./bridge";
import moment, { Moment } from "moment";
import { useHiveClientQuery } from "../core/hive";

export const client = new Client(SERVERS, {
timeout: 3000,
@@ -147,6 +148,7 @@ export interface Reputations {
account: string;
reputation: number;
}

interface ApiError {
error: string;
data: any;
@@ -663,3 +665,17 @@ export const getRcOperationStats = (): Promise<any> => client.call("rc_api", "ge

export const getContentReplies = (author: string, permlink: string): Promise<Entry[] | null> =>
client.call("condenser_api", "get_content_replies", { author, permlink });

export const getPostQuery = (username?: string, permlink?: string) =>
useHiveClientQuery<Entry>("condenser_api", "get_content", [username, permlink], {
enabled: !!username && !!permlink
});

export const getProposalsQuery = () =>
useHiveClientQuery<{ proposals: Proposal[] }>("database_api", "list_proposals", {
start: [-1],
limit: 500,
order: "by_total_votes",
order_direction: "descending",
status: "all"
});
3 changes: 2 additions & 1 deletion src/common/app.tsx
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ import { CoreProvider, EntriesCacheManager } from "./core";

import { UserActivityRecorder } from "./components/user-activity-recorder";
import { BottomStatusBar } from "./components/bottom-status-bar";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

// Define lazy pages
const ProfileContainer = loadable(() => import("./pages/profile-functional"));
@@ -96,7 +97,7 @@ const App = (props: any) => {
<CoreProvider>
<EntriesCacheManager>
{/*Excluded from production*/}
{/*<ReactQueryDevtools initialIsOpen={false} />*/}
<ReactQueryDevtools initialIsOpen={false} />
<Tracker />
<UserActivityRecorder />
<Switch>
2 changes: 1 addition & 1 deletion src/common/components/bottom-status-bar/index.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { LatencyStatusBar } from "./latency-status-bar";

export function BottomStatusBar() {
return (
<div className="fixed flex justify-start bottom-0 left-0 right-0 duration-300 overflow-hidden w-[2rem] border-r border-[--border-color] hover:w-full whitespace-nowrap">
<div className="fixed flex justify-start bottom-0 left-0 right-0 duration-300 overflow-hidden border-r border-[--border-color] hover:w-full whitespace-nowrap">
<LatencyStatusBar />
</div>
);
27 changes: 7 additions & 20 deletions src/common/core/core-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { createContext, PropsWithChildren, useMemo, useRef, useState } from "react";
import { Client } from "@hiveio/dhive";
import useMount from "react-use/lib/useMount";
import { useGetRequestLatency } from "./hive";
import { useFindNearHiveServer } from "./hive";
import SERVERS from "../constants/servers.json";

export const CoreContext = createContext<{
@@ -22,26 +21,14 @@ export function CoreProvider(props: PropsWithChildren<unknown>) {
const [lastLatency, setLastLatency] = useState(0);
const [server, setServer] = useState("");

const { mutateAsync: getRequestLatency } = useGetRequestLatency();
const hiveClient = useMemo(() => hiveClientRef.current, [hiveClientRef.current]);

const hiveClient = useMemo(() => hiveClientRef.current, [hiveClientRef]);

useMount(async () => {
let minLatencyServer: [string | undefined, number] = [undefined, Infinity];
for (const server of SERVERS) {
try {
const { latency } = await getRequestLatency(server);
if (latency < minLatencyServer[1]) {
minLatencyServer = [server, latency];
}
} catch (e) {}
}

if (minLatencyServer[0]) {
setServer(minLatencyServer[0]);
useFindNearHiveServer((server, latency) => {
if (server) {
setServer(server);
}
setLastLatency(minLatencyServer[1]);
hiveClientRef.current = new Client(minLatencyServer[0] ? [minLatencyServer[0]] : SERVERS, {
setLastLatency(latency);
hiveClientRef.current = new Client(server ?? SERVERS, {
timeout: 3000,
failoverThreshold: 3,
consoleOnFailover: true
23 changes: 23 additions & 0 deletions src/common/core/hive/find-near-hive-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import useMount from "react-use/lib/useMount";
import SERVERS from "../../constants/servers.json";
import { useGetRequestLatency } from "./get-request-latency";

export function useFindNearHiveServer(
onFind?: (server: string | undefined, latency: number) => void
) {
const { mutateAsync: getRequestLatency } = useGetRequestLatency();

useMount(async () => {
let minLatencyServer: [string | undefined, number] = [undefined, Infinity];
for (const server of SERVERS) {
try {
const { latency } = await getRequestLatency(server);
if (latency < minLatencyServer[1]) {
minLatencyServer = [server, latency];
}
} catch (e) {}
}

onFind?.(minLatencyServer[0], minLatencyServer[1]);
});
}
11 changes: 6 additions & 5 deletions src/common/core/hive/hive-client-query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext, useEffect } from "react";
import { CoreContext } from "../core-provider";
import { QueryOptions, useQuery } from "@tanstack/react-query";
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { useMappedStore } from "../../store/use-mapped-store";
import { Client } from "@hiveio/dhive";
import { NoHiveClientError } from "./errors";
@@ -10,20 +10,21 @@ import axios from "axios";
export function useHiveClientQuery<DATA>(
api: string,
method: string,
options: QueryOptions<HiveResponseWithLatency<DATA>>
initialParams: Parameters<typeof Client.call>[2],
options?: UseQueryOptions<HiveResponseWithLatency<DATA>>
) {
const { activeUser } = useMappedStore();
const { hiveClient, setLastLatency } = useContext(CoreContext);

const query = useQuery<HiveResponseWithLatency<DATA>>(
["hive", activeUser?.username, api, method],
async (params?: Parameters<typeof Client.call>[2]) => {
["hive", activeUser?.username, api, method, initialParams],
async () => {
if (!hiveClient) {
throw new NoHiveClientError();
}

const startDate = new Date();
const response = await hiveClient.call(api, method, params);
const response = await hiveClient.call(api, method, initialParams);
const endDate = new Date();

return {
1 change: 1 addition & 0 deletions src/common/core/hive/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./hive-client-query";
export * from "./get-request-latency";
export * from "./hive-client-mutation";
export * from "./find-near-hive-server";
173 changes: 71 additions & 102 deletions src/common/pages/proposals.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Fragment } from "react";
import React, { Fragment, useMemo } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { match } from "react-router";
@@ -8,7 +8,6 @@ import _ from "lodash";
import defaults from "../constants/defaults.json";

import { catchPostImage, renderPostBody, setProxyBase } from "@ecency/render-helper";
import { Entry } from "../store/entries/types";

import BaseComponent from "../components/base";
import Meta from "../components/meta";
@@ -24,7 +23,7 @@ import SearchBox from "../components/search-box";
import { _t } from "../i18n";
import { Tsx } from "../i18n/helper";

import { getAccount, getPost, getProposals, Proposal } from "../api/hive";
import { getAccount, getPostQuery, getProposals, getProposalsQuery, Proposal } from "../api/hive";

import { pageMapDispatchToProps, pageMapStateToProps, PageProps } from "./common";

@@ -332,111 +331,81 @@ interface DetailProps extends PageProps {
match: match<MatchParams>;
}

interface DetailState {
loading: boolean;
proposal: Proposal | null;
entry: Entry | null;
}

class ProposalDetailPage extends BaseComponent<DetailProps, DetailState> {
state: DetailState = {
loading: true,
proposal: null,
entry: null
};

componentDidMount() {
this.load();
export function ProposalDetailPage(props: DetailProps) {
const { data: proposalsResponse, isLoading: isProposalsLoading } = getProposalsQuery();

const proposal = useMemo(
() => proposalsResponse?.response.proposals.find((x) => x.id === +props.match.params.id),
[proposalsResponse, props.match.params]
);
const { data: getPostResponse, isLoading: isGetPostLoading } = getPostQuery(
proposal?.creator,
proposal?.permlink
);
const entry = useMemo(() => getPostResponse?.response, [getPostResponse]);
const isLoading = useMemo(
() => isProposalsLoading && isGetPostLoading,
[isProposalsLoading, isGetPostLoading]
);

if (isLoading) {
return (
<>
<NavBar history={props.history} />
<LinearProgress />
</>
);
}

load = () => {
const { match } = this.props;
const proposalId = Number(match.params.id);

this.stateSet({ loading: true });
getProposals()
.then((proposals) => {
const proposal = proposals.find((x) => x.id === proposalId);
if (proposal) {
this.stateSet({ proposal });
return getPost(proposal.creator, proposal.permlink);
}
if (!proposal || !entry) {
return NotFound({ ...props });
}

return null;
})
.then((entry) => {
if (entry) {
this.stateSet({ entry });
}
})
.finally(() => {
this.stateSet({ loading: false });
});
const renderedBody = { __html: renderPostBody(entry.body, false, props.global.canUseWebp) };

// Meta config
const metaProps = {
title: `${_t("proposals.page-title")} | ${proposal.subject}`,
description: `${proposal.subject} by @${proposal.creator}`,
url: `/proposals/${proposal.id}`,
canonical: `/proposals/${proposal.id}`,
published: parseDate(entry.created).toISOString(),
modified: parseDate(entry.updated).toISOString(),
image: catchPostImage(entry.body, 600, 500, props.global.canUseWebp ? "webp" : "match")
};

render() {
const { global } = this.props;
const { loading, proposal, entry } = this.state;

if (loading) {
return (
<>
<NavBar history={this.props.history} />
<LinearProgress />
</>
);
}

if (!proposal || !entry) {
return NotFound({ ...this.props });
}

const renderedBody = { __html: renderPostBody(entry.body, false, global.canUseWebp) };

// Meta config
const metaProps = {
title: `${_t("proposals.page-title")} | ${proposal.subject}`,
description: `${proposal.subject} by @${proposal.creator}`,
url: `/proposals/${proposal.id}`,
canonical: `/proposals/${proposal.id}`,
published: parseDate(entry.created).toISOString(),
modified: parseDate(entry.updated).toISOString(),
image: catchPostImage(entry.body, 600, 500, global.canUseWebp ? "webp" : "match")
};

return (
<>
<Meta {...metaProps} />
<ScrollToTop />
<Theme global={this.props.global} />
<Feedback activeUser={this.props.activeUser} />
<NavBar history={this.props.history} />
<div className="app-content proposals-page proposals-detail-page">
<div className="page-header mt-5">
<h1 className="header-title">{_t("proposals.page-title")}</h1>
<p className="see-all">
<Link to="/proposals">{_t("proposals.see-all")}</Link>
</p>
</div>
<div className="proposal-list">
<Link to="/proposals" className="btn-dismiss">
{closeSvg}
</Link>
{ProposalListItem({
...this.props,
proposal
})}
</div>
<div className="the-entry">
<div
className="entry-body markdown-view user-selectable"
dangerouslySetInnerHTML={renderedBody}
/>
</div>
return (
<>
<Meta {...metaProps} />
<ScrollToTop />
<Theme global={props.global} />
<Feedback activeUser={props.activeUser} />
<NavBar history={props.history} />
<div className="app-content proposals-page proposals-detail-page">
<div className="page-header mt-5">
<h1 className="header-title">{_t("proposals.page-title")}</h1>
<p className="see-all">
<Link to="/proposals">{_t("proposals.see-all")}</Link>
</p>
</div>
</>
);
}
<div className="proposal-list">
<Link to="/proposals" className="btn-dismiss">
{closeSvg}
</Link>
{ProposalListItem({
...props,
proposal
})}
</div>
<div className="the-entry">
<div
className="entry-body markdown-view user-selectable"
dangerouslySetInnerHTML={renderedBody}
/>
</div>
</div>
</>
);
}

export const ProposalDetailContainer = connect(