mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-12 13:49:33 +02:00
use users relays to fetch users notes
cleanup relay urls
This commit is contained in:
parent
effd816707
commit
ce3e61b48a
@ -57,7 +57,9 @@ const RequireCurrentAccount = ({ children }: { children: JSX.Element }) => {
|
||||
const RootPage = () => (
|
||||
<RequireCurrentAccount>
|
||||
<Page>
|
||||
<Outlet />
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</Page>
|
||||
</RequireCurrentAccount>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
Text,
|
||||
useDisclosure,
|
||||
@ -9,6 +9,14 @@ import {
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
Button,
|
||||
TableContainer,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Td,
|
||||
Tr,
|
||||
Th,
|
||||
Flex,
|
||||
} from "@chakra-ui/react";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { useInterval } from "react-use";
|
||||
@ -16,11 +24,14 @@ import { RelayStatus } from "./relay-status";
|
||||
import { useIsMobile } from "../hooks/use-is-mobile";
|
||||
import { RelayIcon } from "./icons";
|
||||
import { Relay } from "../classes/relay";
|
||||
import { RelayFavicon } from "./relay-favicon";
|
||||
import relayScoreboardService from "../services/relay-scoreboard";
|
||||
|
||||
export const ConnectedRelays = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [relays, setRelays] = useState<Relay[]>(relayPoolService.getRelays());
|
||||
const sortedRelays = useMemo(() => relayScoreboardService.getRankedRelays(relays.map((r) => r.url)), [relays]);
|
||||
|
||||
useInterval(() => {
|
||||
setRelays(relayPoolService.getRelays());
|
||||
@ -41,17 +52,43 @@ export const ConnectedRelays = () => {
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="5xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader pb="0">Connected Relays</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
{relays.map((relay) => (
|
||||
<Text key={relay.url}>
|
||||
<RelayStatus url={relay.url} /> {relay.url}
|
||||
</Text>
|
||||
))}
|
||||
<ModalBody p="2">
|
||||
<TableContainer>
|
||||
<Table size="sm">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Relay</Th>
|
||||
<Th isNumeric>Claims</Th>
|
||||
<Th isNumeric>Avg Response</Th>
|
||||
<Th isNumeric>Disconnects</Th>
|
||||
<Th isNumeric>Status</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{sortedRelays.map((url) => (
|
||||
<Tr key={url}>
|
||||
<Td>
|
||||
<Flex alignItems="center" maxW="sm" overflow="hidden">
|
||||
<RelayFavicon size="xs" relay={url} mr="2" />
|
||||
<Text>{url}</Text>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td isNumeric>{relayPoolService.getRelayClaims(url).size}</Td>
|
||||
<Td isNumeric>{relayScoreboardService.getAverageResponseTime(url).toFixed(2)}ms</Td>
|
||||
<Td isNumeric>{relayScoreboardService.getDisconnects(url)}</Td>
|
||||
<Td isNumeric>
|
||||
<RelayStatus url={url} />
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
12
src/helpers/relay.ts
Normal file
12
src/helpers/relay.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { RelayConfig } from "../classes/relay";
|
||||
import { safeRelayUrl } from "./url";
|
||||
|
||||
export function normalizeRelayConfigs(relays: RelayConfig[]) {
|
||||
return relays.reduce((newArr, r) => {
|
||||
const safeUrl = safeRelayUrl(r.url);
|
||||
if (safeUrl) {
|
||||
newArr.push({ ...r, url: safeUrl });
|
||||
}
|
||||
return newArr;
|
||||
}, [] as RelayConfig[]);
|
||||
}
|
@ -1,17 +1,20 @@
|
||||
import { utils } from "nostr-tools";
|
||||
|
||||
export function validateRelayUrl(relayUrl: string) {
|
||||
const normalized = utils.normalizeURL(relayUrl);
|
||||
const url = new URL(normalized);
|
||||
export function normalizeRelayUrl(relayUrl: string) {
|
||||
const url = new URL(relayUrl);
|
||||
|
||||
if (url.protocol !== "wss:" && url.protocol !== "ws:") throw new Error("Incorrect protocol");
|
||||
|
||||
return url.toString();
|
||||
url.pathname = url.pathname.replace(/\/+/g, "/");
|
||||
if (url.pathname.endsWith("/")) url.pathname = url.pathname.slice(0, -1);
|
||||
if ((url.port === "80" && url.protocol === "ws:") || (url.port === "443" && url.protocol === "wss:")) url.port = "";
|
||||
url.searchParams.sort();
|
||||
url.hash = "";
|
||||
|
||||
return url.origin + (url.pathname === "/" ? "" : url.pathname) + url.search;
|
||||
}
|
||||
|
||||
export function safeRelayUrl(relayUrl: string) {
|
||||
try {
|
||||
return validateRelayUrl(relayUrl);
|
||||
return normalizeRelayUrl(relayUrl);
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
26
src/hooks/use-merged-user-relays.tsx
Normal file
26
src/hooks/use-merged-user-relays.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { useMemo } from "react";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||
import relayScoreboardService from "../services/relay-scoreboard";
|
||||
import { useUserContacts } from "./use-user-contacts";
|
||||
import { useUserRelays } from "./use-user-relays";
|
||||
|
||||
export default function useMergedUserRelays(pubkey: string) {
|
||||
const contacts = useUserContacts(pubkey);
|
||||
const userRelays = useUserRelays(pubkey);
|
||||
|
||||
return useMemo(() => {
|
||||
let relays: RelayConfig[] = userRelays?.relays ?? [];
|
||||
|
||||
// use the relays stored in contacts if there are no relay config
|
||||
if (relays.length === 0 && contacts) {
|
||||
relays = contacts.relays;
|
||||
}
|
||||
|
||||
// normalize relay urls and remove bad ones
|
||||
const normalized = normalizeRelayConfigs(relays);
|
||||
|
||||
const rankedUrls = relayScoreboardService.getRankedRelays(normalized.map((r) => r.url));
|
||||
return rankedUrls.map((u) => normalized.find((r) => r.url === u) as RelayConfig);
|
||||
}, [userRelays, contacts]);
|
||||
}
|
@ -5,12 +5,11 @@ import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export function useUserContacts(pubkey: string, additionalRelays: string[] = [], alwaysRequest = false) {
|
||||
const clientRelays = useReadRelayUrls();
|
||||
const relays = useMemo(() => unique(clientRelays.concat(additionalRelays)), [additionalRelays.join(",")]);
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
|
||||
const observable = useMemo(
|
||||
() => userContactsService.requestContacts(pubkey, relays, alwaysRequest),
|
||||
[pubkey, relays, alwaysRequest]
|
||||
() => userContactsService.requestContacts(pubkey, readRelays, alwaysRequest),
|
||||
[pubkey, readRelays, alwaysRequest]
|
||||
);
|
||||
const contacts = useSubject(observable);
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { useMemo } from "react";
|
||||
import { unique } from "../helpers/array";
|
||||
import userRelaysService from "../services/user-relays";
|
||||
import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export function useUserRelays(pubkey: string, additionalRelays: string[] = [], alwaysRequest = false) {
|
||||
const clientRelays = useReadRelayUrls();
|
||||
const relays = useMemo(() => unique(clientRelays.concat(additionalRelays)), [additionalRelays.join(",")]);
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
|
||||
const observable = useMemo(
|
||||
() => userRelaysService.requestRelays(pubkey, relays, alwaysRequest),
|
||||
[pubkey, relays, alwaysRequest]
|
||||
() => userRelaysService.requestRelays(pubkey, readRelays, alwaysRequest),
|
||||
[pubkey, readRelays.join("|"), alwaysRequest]
|
||||
);
|
||||
const contacts = useSubject(observable);
|
||||
const userRelays = useSubject(observable);
|
||||
|
||||
return contacts;
|
||||
return userRelays;
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ import { createRoot } from "react-dom/client";
|
||||
import { App } from "./app";
|
||||
import { Providers } from "./providers";
|
||||
|
||||
import "./services/pubkey-relay-weights";
|
||||
|
||||
const element = document.getElementById("root");
|
||||
if (!element) throw new Error("missing mount point");
|
||||
const root = createRoot(element);
|
||||
|
@ -38,8 +38,6 @@ const MIGRATIONS: MigrationFunction[] = [
|
||||
dnsIdentifiers.createIndex("domain", "domain", { unique: false });
|
||||
dnsIdentifiers.createIndex("updated", "updated", { unique: false });
|
||||
|
||||
db.createObjectStore("pubkeyRelayWeights", { keyPath: "pubkey" });
|
||||
|
||||
db.createObjectStore("settings");
|
||||
db.createObjectStore("relayInfo");
|
||||
db.createObjectStore("relayScoreboardStats", { keyPath: "relay" });
|
||||
@ -69,7 +67,7 @@ export async function clearCacheData() {
|
||||
await db.clear("userRelays");
|
||||
await db.clear("relayInfo");
|
||||
await db.clear("dnsIdentifiers");
|
||||
await db.clear("pubkeyRelayWeights");
|
||||
await db.clear("relayScoreboardStats");
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,6 @@ export interface CustomSchema extends DBSchema {
|
||||
indexes: { name: string; domain: string; pubkey: string; updated: number };
|
||||
};
|
||||
relayInfo: { key: string; value: RelayInformationDocument };
|
||||
pubkeyRelayWeights: {
|
||||
key: string;
|
||||
value: { pubkey: string; relays: Record<string, number>; updated: number };
|
||||
indexes: { pubkey: string };
|
||||
};
|
||||
relayScoreboardStats: {
|
||||
key: string;
|
||||
value: { relay: string; responseTimes: [number, Date][]; disconnects: Date[] };
|
||||
|
@ -1,70 +0,0 @@
|
||||
import moment from "moment";
|
||||
import db from "./db";
|
||||
import { UserContacts } from "./user-contacts";
|
||||
|
||||
const changed = new Set();
|
||||
const cache: Record<string, Record<string, number> | undefined> = {};
|
||||
|
||||
async function populateCacheFromDb(pubkey: string) {
|
||||
if (!cache[pubkey]) {
|
||||
cache[pubkey] = (await db.get("pubkeyRelayWeights", pubkey))?.relays;
|
||||
}
|
||||
}
|
||||
|
||||
async function addWeight(pubkey: string, relay: string, weight: number = 1) {
|
||||
await populateCacheFromDb(pubkey);
|
||||
|
||||
const relays = cache[pubkey] || (cache[pubkey] = {});
|
||||
|
||||
if (relays[relay]) {
|
||||
relays[relay] += weight;
|
||||
} else {
|
||||
relays[relay] = weight;
|
||||
}
|
||||
changed.add(pubkey);
|
||||
}
|
||||
|
||||
async function saveCache() {
|
||||
const now = moment().unix();
|
||||
const transaction = db.transaction("pubkeyRelayWeights", "readwrite");
|
||||
|
||||
for (const [pubkey, relays] of Object.entries(cache)) {
|
||||
if (changed.has(pubkey)) {
|
||||
if (relays) transaction.store?.put({ pubkey, relays, updated: now });
|
||||
}
|
||||
}
|
||||
changed.clear();
|
||||
}
|
||||
|
||||
async function handleContactList(contacts: UserContacts) {
|
||||
// save the relays for contacts
|
||||
for (const [pubkey, relay] of Object.entries(contacts.contactRelay)) {
|
||||
if (relay) await addWeight(pubkey, relay);
|
||||
}
|
||||
|
||||
// save this pubkeys relays
|
||||
for (const [relay, opts] of Object.entries(contacts.relays)) {
|
||||
// only save relays this users writes to
|
||||
if (opts.write) {
|
||||
await addWeight(contacts.pubkey, relay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getPubkeyRelays(pubkey: string) {
|
||||
await populateCacheFromDb(pubkey);
|
||||
return cache[pubkey] || {};
|
||||
}
|
||||
const pubkeyRelayWeightsService = {
|
||||
handleContactList,
|
||||
getPubkeyRelays,
|
||||
};
|
||||
|
||||
setInterval(() => saveCache(), 1000);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.pubkeyRelayWeightsService = pubkeyRelayWeightsService;
|
||||
}
|
||||
|
||||
export default pubkeyRelayWeightsService;
|
@ -66,6 +66,11 @@ export class RelayPoolService {
|
||||
|
||||
const relayPoolService = new RelayPoolService();
|
||||
|
||||
setInterval(() => {
|
||||
relayPoolService.reconnectRelays();
|
||||
relayPoolService.pruneRelays();
|
||||
}, 1000 * 15);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.relayPoolService = relayPoolService;
|
||||
|
@ -47,8 +47,7 @@ class RelayScoreboardService {
|
||||
|
||||
getAverageResponseTime(relay: string) {
|
||||
const times = this.relayResponseTimes.get(relay);
|
||||
// TODO: not sure if this is a good fix for keeping unconnected relays from the top of the list
|
||||
if (times.length === 0) return 200;
|
||||
if (times.length === 0) return Infinity;
|
||||
const total = times.reduce((total, [time]) => total + time, 0);
|
||||
return total / times.length;
|
||||
}
|
||||
|
@ -4,17 +4,31 @@ import db from "./db";
|
||||
import { CachedPubkeyEventRequester } from "../classes/cached-pubkey-event-requester";
|
||||
import { SuperMap } from "../classes/super-map";
|
||||
import Subject from "../classes/subject";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
|
||||
export type UserContacts = {
|
||||
pubkey: string;
|
||||
relays: Record<string, { read: boolean; write: boolean }>;
|
||||
relays: RelayConfig[];
|
||||
contacts: string[];
|
||||
contactRelay: Record<string, string | undefined>;
|
||||
created_at: number;
|
||||
};
|
||||
|
||||
type RelayJson = Record<string, { read: boolean; write: boolean }>;
|
||||
function relayJsonToRelayConfig(relayJson: RelayJson) {
|
||||
try {
|
||||
return Array.from(Object.entries(relayJson)).map(([url, opts]) => ({
|
||||
url,
|
||||
mode: (opts.write ? RelayMode.WRITE : 0) | (opts.read ? RelayMode.READ : 0),
|
||||
}));
|
||||
} catch (e) {}
|
||||
return [];
|
||||
}
|
||||
|
||||
function parseContacts(event: NostrEvent): UserContacts {
|
||||
const relays = safeJson(event.content, {}) as UserContacts["relays"];
|
||||
const relayJson = safeJson(event.content, {}) as RelayJson;
|
||||
const relays = relayJsonToRelayConfig(relayJson);
|
||||
|
||||
const pubkeys = event.tags.filter(isPTag).map((tag) => tag[1]);
|
||||
const contactRelay = event.tags.filter(isPTag).reduce((dir, tag) => {
|
||||
if (tag[2]) {
|
||||
|
@ -83,7 +83,7 @@ function receiveEvent(event: NostrEvent) {
|
||||
}
|
||||
|
||||
subscription.onEvent.subscribe((event) => {
|
||||
// pass the event ot the contacts service
|
||||
// pass the event to the contacts service
|
||||
userContactsService.receiveEvent(event);
|
||||
receiveEvent(event);
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ import { RelayUrlInput } from "../../components/relay-url-input";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { RelayStatus } from "../../components/relay-status";
|
||||
import relayScoreboardService from "../../services/relay-scoreboard";
|
||||
import { validateRelayUrl } from "../../helpers/url";
|
||||
import { normalizeRelayUrl } from "../../helpers/url";
|
||||
|
||||
export default function RelaysView() {
|
||||
const relays = useSubject(clientRelaysService.relays);
|
||||
@ -54,7 +54,7 @@ export default function RelaysView() {
|
||||
const handleAddRelay = (event: SyntheticEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const url = validateRelayUrl(relayInputValue);
|
||||
const url = normalizeRelayUrl(relayInputValue);
|
||||
if (!relays.some((r) => r.url === url) && !pendingAdd.some((r) => r.url === url)) {
|
||||
addActions.push({ url, mode: RelayMode.ALL });
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import { Flex, Spinner, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import { Outlet, useLoaderData, useMatches, useNavigate } from "react-router-dom";
|
||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import { getUserDisplayName } from "../../helpers/user-metadata";
|
||||
@ -6,6 +6,7 @@ import { useIsMobile } from "../../hooks/use-is-mobile";
|
||||
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import Header from "./components/header";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const tabs = [
|
||||
{ label: "Notes", path: "notes" },
|
||||
@ -52,7 +53,9 @@ const UserView = () => {
|
||||
<TabPanels overflow={isMobile ? undefined : "auto"} height="100%">
|
||||
{tabs.map(({ label }) => (
|
||||
<TabPanel key={label} pr={0} pl={0}>
|
||||
<Outlet context={{ pubkey }} />
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Outlet context={{ pubkey }} />
|
||||
</Suspense>
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
|
@ -1,14 +1,39 @@
|
||||
import { Button, Flex, FormControl, FormLabel, Spinner, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
ListItem,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverCloseButton,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Spinner,
|
||||
Switch,
|
||||
UnorderedList,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import { RelayIcon } from "../../components/icons";
|
||||
import { Note } from "../../components/note";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import useMergedUserRelays from "../../hooks/use-merged-user-relays";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
|
||||
const UserNotesTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const relays = useReadRelayUrls();
|
||||
const userRelays = useMergedUserRelays(pubkey);
|
||||
const relays = userRelays
|
||||
.filter((r) => r.mode & RelayMode.WRITE)
|
||||
.map((r) => r.url)
|
||||
.filter(Boolean)
|
||||
.slice(0, 4) as string[];
|
||||
|
||||
const { isOpen: showReplies, onToggle: toggleReplies } = useDisclosure();
|
||||
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
@ -26,6 +51,25 @@ const UserNotesTab = () => {
|
||||
Show Replies
|
||||
</FormLabel>
|
||||
<Switch id="show-replies" isChecked={showReplies} onChange={toggleReplies} />
|
||||
<Box flexGrow={1} />
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<Button variant="link" leftIcon={<RelayIcon />}>
|
||||
Using Relays
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverCloseButton />
|
||||
<PopoverBody>
|
||||
<UnorderedList>
|
||||
{relays.map((url) => (
|
||||
<ListItem key={url}>{url}</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</FormControl>
|
||||
{timeline.map((event) => (
|
||||
<Note key={event.id} event={event} maxHeight={300} />
|
||||
|
@ -1,38 +1,18 @@
|
||||
import { Text, Grid, Box, IconButton, Flex, Heading, Badge, Alert, AlertIcon } from "@chakra-ui/react";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import { Text, Box, IconButton, Flex, Badge } from "@chakra-ui/react";
|
||||
import { useNavigate, useOutletContext } from "react-router-dom";
|
||||
import { GlobalIcon } from "../../components/icons";
|
||||
import { useUserRelays } from "../../hooks/use-user-relays";
|
||||
import { useMemo } from "react";
|
||||
import relayScoreboardService from "../../services/relay-scoreboard";
|
||||
import { RelayConfig, RelayMode } from "../../classes/relay";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import useMergedUserRelays from "../../hooks/use-merged-user-relays";
|
||||
|
||||
const UserRelaysTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const contacts = useUserContacts(pubkey);
|
||||
const userRelays = useUserRelays(pubkey);
|
||||
const userRelays = useMergedUserRelays(pubkey);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const relays = useMemo(() => {
|
||||
let arr: RelayConfig[] = userRelays?.relays ?? [];
|
||||
if (arr.length === 0 && contacts)
|
||||
arr = Array.from(Object.entries(contacts.relays)).map(([url, opts]) => ({
|
||||
url,
|
||||
mode: (opts.write ? RelayMode.WRITE : 0) | (opts.read ? RelayMode.READ : 0),
|
||||
}));
|
||||
const rankedUrls = relayScoreboardService.getRankedRelays(arr.map((r) => r.url));
|
||||
return rankedUrls.map((u) => arr.find((r) => r.url === u) as RelayConfig);
|
||||
}, [userRelays, contacts]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
{!userRelays && contacts?.relays && (
|
||||
<Alert status="warning">
|
||||
<AlertIcon />
|
||||
Cant find new relay list
|
||||
</Alert>
|
||||
)}
|
||||
{relays.map((relayConfig) => (
|
||||
{userRelays.map((relayConfig) => (
|
||||
<Box key={relayConfig.url} display="flex" gap="2" alignItems="center" pr="2" pl="2">
|
||||
<Text flex={1}>{relayConfig.url}</Text>
|
||||
<Text>{relayScoreboardService.getAverageResponseTime(relayConfig.url).toFixed(2)}ms</Text>
|
||||
|
Loading…
x
Reference in New Issue
Block a user