From 240fe8bc7c3a8b8a5f710a94c532b106f0d864e4 Mon Sep 17 00:00:00 2001 From: reya Date: Thu, 18 Jan 2024 09:41:53 +0700 Subject: [PATCH] feat: fix relay manager --- .../routes/relays/components/relayItem.tsx | 30 +++++++++++---- .../routes/relays/components/relayList.tsx | 4 +- .../src/routes/relays/components/sidebar.tsx | 38 ++++++++++++------- apps/desktop/src/routes/relays/follows.tsx | 25 +++++++++--- apps/desktop/src/routes/relays/global.tsx | 1 + packages/ark/src/ark.ts | 27 ++++++++----- packages/ark/src/hooks/useRelayList.ts | 23 +++++------ 7 files changed, 98 insertions(+), 50 deletions(-) diff --git a/apps/desktop/src/routes/relays/components/relayItem.tsx b/apps/desktop/src/routes/relays/components/relayItem.tsx index 171f404f..72754927 100644 --- a/apps/desktop/src/routes/relays/components/relayItem.tsx +++ b/apps/desktop/src/routes/relays/components/relayItem.tsx @@ -1,9 +1,9 @@ -import { useRelaylist } from "@lume/ark"; -import { PlusIcon, ShareIcon } from "@lume/icons"; +import { User, useRelaylist } from "@lume/ark"; +import { PlusIcon, SearchIcon } from "@lume/icons"; import { normalizeRelayUrl } from "nostr-fetch"; import { Link } from "react-router-dom"; -export function RelayItem({ url }: { url: string }) { +export function RelayItem({ url, users }: { url: string; users?: string[] }) { const domain = new URL(url).hostname; const { connectRelay } = useRelaylist(); @@ -18,19 +18,35 @@ export function RelayItem({ url }: { url: string }) {
+ {users ? ( +
+ {users.slice(0, 4).map((item) => ( + + + + + + ))} + {users.length > 4 ? ( +
+ +{users.length - 4} +
+ ) : null} +
+ ) : null} - + Inspect
diff --git a/apps/desktop/src/routes/relays/components/relayList.tsx b/apps/desktop/src/routes/relays/components/relayList.tsx index ef84d8cd..9d7c7458 100644 --- a/apps/desktop/src/routes/relays/components/relayList.tsx +++ b/apps/desktop/src/routes/relays/components/relayList.tsx @@ -1,4 +1,4 @@ -import { useArk, useRelay } from "@lume/ark"; +import { useArk, useRelaylist } from "@lume/ark"; import { LoaderIcon, PlusIcon, ShareIcon } from "@lume/icons"; import { User } from "@lume/ui"; import { useQuery } from "@tanstack/react-query"; @@ -7,7 +7,7 @@ import { VList } from "virtua"; export function RelayList() { const ark = useArk(); - const { connectRelay } = useRelay(); + const { connectRelay } = useRelaylist(); const { status, data } = useQuery({ queryKey: ["relays"], queryFn: async () => { diff --git a/apps/desktop/src/routes/relays/components/sidebar.tsx b/apps/desktop/src/routes/relays/components/sidebar.tsx index 93fd441d..89b49315 100644 --- a/apps/desktop/src/routes/relays/components/sidebar.tsx +++ b/apps/desktop/src/routes/relays/components/sidebar.tsx @@ -1,14 +1,15 @@ -import { useArk } from "@lume/ark"; -import { CancelIcon, RefreshIcon } from "@lume/icons"; +import { useArk, useRelaylist } from "@lume/ark"; +import { CancelIcon, LoaderIcon, RefreshIcon } from "@lume/icons"; import { cn } from "@lume/utils"; -import { NDKKind } from "@nostr-dev-kit/ndk"; +import { NDKKind, NDKSubscriptionCacheUsage } from "@nostr-dev-kit/ndk"; import { useQuery } from "@tanstack/react-query"; import { RelayForm } from "./relayForm"; export function RelaySidebar({ className }: { className?: string }) { const ark = useArk(); - const { status, data, refetch } = useQuery({ + const { removeRelay } = useRelaylist(); + const { status, data, isRefetching, refetch } = useQuery({ queryKey: ["relay-personal"], queryFn: async () => { const event = await ark.getEventByFilter({ @@ -16,12 +17,15 @@ export function RelaySidebar({ className }: { className?: string }) { kinds: [NDKKind.RelayList], authors: [ark.account.pubkey], }, + cache: NDKSubscriptionCacheUsage.ONLY_RELAY, }); - if (!event) return []; return event.tags.filter((tag) => tag[0] === "r"); }, refetchOnWindowFocus: false, + refetchOnMount: false, + refetchOnReconnect: false, + staleTime: Infinity, }); const currentRelays = new Set( @@ -42,17 +46,19 @@ export function RelaySidebar({ className }: { className?: string }) { onClick={() => refetch()} className="inline-flex items-center justify-center w-6 h-6 rounded-md shrink-0 hover:bg-neutral-100 dark:hover:bg-neutral-900" > - +
{status === "pending" ? ( -

Loading...

+
+ +
) : !data.length ? ( -
-

- You not have personal relay list yet -

+
+

Empty.

) : ( data.map((item) => ( @@ -73,7 +79,10 @@ export function RelaySidebar({ className }: { className?: string }) { )}

- {item[1].replace("wss://", "").replace("ws://", "")} + {item[1] + .replace("wss://", "") + .replace("ws://", "") + .replace("/", "")}

@@ -84,9 +93,10 @@ export function RelaySidebar({ className }: { className?: string }) { ) : null}
diff --git a/apps/desktop/src/routes/relays/follows.tsx b/apps/desktop/src/routes/relays/follows.tsx index afddd214..6b1a31e4 100644 --- a/apps/desktop/src/routes/relays/follows.tsx +++ b/apps/desktop/src/routes/relays/follows.tsx @@ -1,18 +1,25 @@ import { useArk } from "@lume/ark"; -import { LoaderIcon, PlusIcon, ShareIcon } from "@lume/icons"; +import { LoaderIcon } from "@lume/icons"; import { useQuery } from "@tanstack/react-query"; import { VList } from "virtua"; import { RelayItem } from "./components/relayItem"; export function RelayFollowsScreen() { const ark = useArk(); - const { isLoading, data: relays } = useQuery({ + const { + isLoading, + isError, + data: relays, + } = useQuery({ queryKey: ["relay-follows"], queryFn: async ({ signal }: { signal: AbortSignal }) => { - return await ark.getAllRelaysFromContacts(); + const data = await ark.getAllRelaysFromContacts({ signal }); + if (!data) throw new Error("Failed to get relay list from contacts"); + return data; }, refetchOnMount: false, refetchOnWindowFocus: false, + refetchOnReconnect: false, }); if (isLoading) { @@ -23,10 +30,18 @@ export function RelayFollowsScreen() { ); } + if (isError || !relays) { + return ( +
+

Error

+
+ ); + } + return ( - {relays.map((item: string) => ( - + {[...relays].map(([key, value]) => ( + ))} ); diff --git a/apps/desktop/src/routes/relays/global.tsx b/apps/desktop/src/routes/relays/global.tsx index 249119f9..dba51189 100644 --- a/apps/desktop/src/routes/relays/global.tsx +++ b/apps/desktop/src/routes/relays/global.tsx @@ -14,6 +14,7 @@ export function RelayGlobalScreen() { }, refetchOnMount: false, refetchOnWindowFocus: false, + refetchOnReconnect: false, }); if (isLoading) { diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index c092b641..a8173d54 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -254,9 +254,12 @@ export class Ark { } } - public async getEventByFilter({ filter }: { filter: NDKFilter }) { + public async getEventByFilter({ + filter, + cache, + }: { filter: NDKFilter; cache?: NDKSubscriptionCacheUsage }) { const event = await this.ndk.fetchEvent(filter, { - cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST, + cacheUsage: cache || NDKSubscriptionCacheUsage.CACHE_FIRST, }); if (!event) return null; @@ -352,7 +355,7 @@ export class Ark { } } - public async getAllRelaysFromContacts() { + public async getAllRelaysFromContacts({ signal }: { signal: AbortSignal }) { const fetcher = NostrFetcher.withCustomPool(ndkAdapter(this.ndk)); const connectedRelays = this.ndk.pool .connectedRelays() @@ -367,15 +370,21 @@ export class Ark { }, { kinds: [NDKKind.RelayList] }, 1, + { abortSignal: signal }, ); - for await (const { author, events } of relayEvents) { - if (events[0]) { - for (const tag of events[0].tags) { - const users = relayMap.get(tag[1]); + console.log(relayEvents); - if (!users) relayMap.set(tag[1], [author]); - users.push(author); + for await (const { author, events } of relayEvents) { + if (events.length) { + const relayTags = events[0].tags.filter((item) => item[0] === "r"); + for (const tag of relayTags) { + const item = relayMap.get(tag[1]); + if (item?.length) { + item.push(author); + } else { + relayMap.set(tag[1], [author]); + } } } } diff --git a/packages/ark/src/hooks/useRelayList.ts b/packages/ark/src/hooks/useRelayList.ts index 19817ffc..85753c69 100644 --- a/packages/ark/src/hooks/useRelayList.ts +++ b/packages/ark/src/hooks/useRelayList.ts @@ -1,5 +1,6 @@ -import { NDKKind, NDKRelayUrl, NDKTag } from "@nostr-dev-kit/ndk"; +import { NDKKind, NDKTag } from "@nostr-dev-kit/ndk"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { normalizeRelayUrl } from "nostr-fetch"; import { useArk } from "./useArk"; export function useRelaylist() { @@ -8,7 +9,7 @@ export function useRelaylist() { const connectRelay = useMutation({ mutationFn: async ( - relay: NDKRelayUrl, + relay: WebSocket["url"], purpose?: "read" | "write" | undefined, ) => { // Cancel any outgoing refetches @@ -16,11 +17,10 @@ export function useRelaylist() { queryKey: ["relay-personal"], }); + const relayUrl = normalizeRelayUrl(relay); + // Snapshot the previous value - const prevRelays: NDKTag[] = queryClient.getQueryData([ - "relays", - ark.account.pubkey, - ]); + const prevRelays: NDKTag[] = queryClient.getQueryData(["relay-personal"]); // create new relay list if not exist if (!prevRelays) { @@ -36,13 +36,13 @@ export function useRelaylist() { await ark.createEvent({ kind: NDKKind.RelayList, - tags: [...prevRelays, ["r", relay, purpose ?? ""]], + tags: [...prevRelays, ["r", relayUrl, purpose ?? ""]], }); // Optimistically update to the new value queryClient.setQueryData(["relay-personal"], (prev: NDKTag[]) => [ ...prev, - ["r", relay, purpose ?? ""], + ["r", relayUrl, purpose ?? ""], ]); // Return a context object with the snapshotted value @@ -56,17 +56,14 @@ export function useRelaylist() { }); const removeRelay = useMutation({ - mutationFn: async (relay: NDKRelayUrl) => { + mutationFn: async (relay: WebSocket["url"]) => { // Cancel any outgoing refetches await queryClient.cancelQueries({ queryKey: ["relay-personal"], }); // Snapshot the previous value - const prevRelays: NDKTag[] = queryClient.getQueryData([ - "relays", - ark.account.pubkey, - ]); + const prevRelays: NDKTag[] = queryClient.getQueryData(["relay-personal"]); if (!prevRelays) return;