From 255dcb43fe54aa415ad53817955c682c3062ef67 Mon Sep 17 00:00:00 2001 From: reya Date: Sat, 2 Dec 2023 17:53:45 +0700 Subject: [PATCH] improve relay form --- src/app/auth/create.tsx | 17 +++- src/app/auth/follow.tsx | 5 +- src/app/relays/components/relayForm.tsx | 59 ++++++------ src/app/relays/components/relayList.tsx | 30 ++----- .../{userRelay.tsx => userRelayList.tsx} | 67 ++++++++------ src/app/relays/index.tsx | 11 +-- src/libs/ndk/instance.ts | 2 +- src/utils/hooks/useRelay.ts | 89 +++++++++++++++++++ 8 files changed, 178 insertions(+), 102 deletions(-) rename src/app/relays/components/{userRelay.tsx => userRelayList.tsx} (52%) create mode 100644 src/utils/hooks/useRelay.ts diff --git a/src/app/auth/create.tsx b/src/app/auth/create.tsx index a5e38ab4..aa8f5d40 100644 --- a/src/app/auth/create.tsx +++ b/src/app/auth/create.tsx @@ -1,7 +1,7 @@ import { NDKEvent, NDKKind, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; import { downloadDir } from '@tauri-apps/api/path'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; -import { message, save } from '@tauri-apps/plugin-dialog'; +import { save } from '@tauri-apps/plugin-dialog'; import { writeTextFile } from '@tauri-apps/plugin-fs'; import { motion } from 'framer-motion'; import { minidenticon } from 'minidenticons'; @@ -67,7 +67,6 @@ export function CreateAccountScreen() { const event = new NDKEvent(ndk); event.content = JSON.stringify(profile); event.kind = NDKKind.Metadata; - event.created_at = Math.floor(Date.now() / 1000); event.pubkey = userPubkey; event.tags = []; @@ -76,6 +75,16 @@ export function CreateAccountScreen() { if (publish) { await db.createAccount(userNpub, userPubkey); await db.secureSave(userPubkey, userPrivkey); + + const relayListEvent = new NDKEvent(ndk); + relayListEvent.kind = NDKKind.RelayList; + relayListEvent.tags = [...ndk.pool.relays.values()].map((item) => [ + 'r', + item.url, + ]); + + await relayListEvent.publish(); + setKeys({ npub: userNpub, nsec: userNsec, @@ -88,7 +97,7 @@ export function CreateAccountScreen() { setLoading(false); } } catch (e) { - return toast(e); + return toast.error(e); } }; @@ -113,7 +122,7 @@ export function CreateAccountScreen() { setDownloaded(true); } // else { user cancel action } } catch (e) { - await message(e, { title: 'Cannot download account keys', type: 'error' }); + return toast.error(e); } }; diff --git a/src/app/auth/follow.tsx b/src/app/auth/follow.tsx index 24de8028..cf436143 100644 --- a/src/app/auth/follow.tsx +++ b/src/app/auth/follow.tsx @@ -66,7 +66,7 @@ export function FollowScreen() { const submit = async () => { try { setLoading(true); - if (!follows.length) navigate('/'); + if (!follows.length) return navigate('/auth/finish'); const event = new NDKEvent(ndk); event.kind = NDKKind.Contacts; @@ -81,7 +81,8 @@ export function FollowScreen() { if (item.startsWith('npub')) return nip19.decode(item).data as string; return item; }); - navigate('/auth/finish'); + + return navigate('/auth/finish'); } } catch (e) { setLoading(false); diff --git a/src/app/relays/components/relayForm.tsx b/src/app/relays/components/relayForm.tsx index 63115414..20d1ea50 100644 --- a/src/app/relays/components/relayForm.tsx +++ b/src/app/relays/components/relayForm.tsx @@ -1,69 +1,62 @@ -import { useQueryClient } from '@tanstack/react-query'; +import { NDKRelayUrl } from '@nostr-dev-kit/ndk'; +import { normalizeRelayUrl } from 'nostr-fetch'; import { useState } from 'react'; - -import { useStorage } from '@libs/storage/provider'; +import { toast } from 'sonner'; import { PlusIcon } from '@shared/icons'; +import { useRelay } from '@utils/hooks/useRelay'; + const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/; export function RelayForm() { - const { db } = useStorage(); - const queryClient = useQueryClient(); + const { connectRelay } = useRelay(); + const [relay, setRelay] = useState<{ + url: NDKRelayUrl; + purpose: 'read' | 'write' | undefined; + }>({ url: '', purpose: undefined }); - const [url, setUrl] = useState(''); - const [error, setError] = useState(''); - - const createRelay = async () => { - if (url.length < 1) return setError('Please enter relay url'); + const create = () => { + if (relay.url.length < 1) return toast.info('Please enter relay url'); try { - const relay = new URL(url.replace(/\s/g, '')); + const relayUrl = new URL(relay.url.replace(/\s/g, '')); if ( - domainRegex.test(relay.host) && - (relay.protocol === 'wss:' || relay.protocol === 'ws:') + domainRegex.test(relayUrl.host) && + (relayUrl.protocol === 'wss:' || relayUrl.protocol === 'ws:') ) { - const res = await db.createRelay(url); - if (!res) return setError("You're already using this relay"); - - queryClient.invalidateQueries({ - queryKey: ['user-relay'], - }); - - setError(''); - setUrl(''); + connectRelay.mutate(normalizeRelayUrl(relay.url)); + setRelay({ url: '', purpose: undefined }); } else { - return setError( + return toast.error( 'URL is invalid, a relay must use websocket protocol (start with wss:// or ws://). Please check again' ); } } catch { - return setError('Relay URL is not valid. Please check again'); + return toast.error('Relay URL is not valid. Please check again'); } }; return (
-
+
setUrl(e.target.value)} + value={relay.url} + onChange={(e) => setRelay((prev) => ({ ...prev, url: e.target.value }))} />
- {error}
); } diff --git a/src/app/relays/components/relayList.tsx b/src/app/relays/components/relayList.tsx index 860ce1e7..ec77917a 100644 --- a/src/app/relays/components/relayList.tsx +++ b/src/app/relays/components/relayList.tsx @@ -1,22 +1,18 @@ -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { normalizeRelayUrl } from 'nostr-fetch'; +import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; -import { toast } from 'sonner'; import { VList } from 'virtua'; -import { useStorage } from '@libs/storage/provider'; - import { LoaderIcon, PlusIcon, ShareIcon } from '@shared/icons'; import { User } from '@shared/user'; import { useNostr } from '@utils/hooks/useNostr'; +import { useRelay } from '@utils/hooks/useRelay'; export function RelayList() { const navigate = useNavigate(); - const queryClient = useQueryClient(); const { getAllRelaysByUsers } = useNostr(); - const { db } = useStorage(); + const { connectRelay } = useRelay(); const { status, data } = useQuery({ queryKey: ['relays'], queryFn: async () => { @@ -33,20 +29,6 @@ export function RelayList() { navigate(`/relays/${url.hostname}`); }; - const connectRelay = async (relayUrl: string) => { - const url = normalizeRelayUrl(relayUrl); - const res = await db.createRelay(url); - - if (res) { - toast.info('Connected. You need to restart app to take effect'); - queryClient.invalidateQueries({ - queryKey: ['user-relay'], - }); - } else { - toast.warning("You're aldready connected to this relay"); - } - }; - return (
{status === 'pending' ? ( @@ -59,9 +41,7 @@ export function RelayList() { ) : (
-

- All relays -

+

Relay discovery

{[...data].map(([key, value]) => (
- ))} - -
- )} + )) + )} + +
); } diff --git a/src/app/relays/index.tsx b/src/app/relays/index.tsx index 73a88384..1fb8774f 100644 --- a/src/app/relays/index.tsx +++ b/src/app/relays/index.tsx @@ -1,18 +1,11 @@ import { RelayList } from '@app/relays/components/relayList'; -import { UserRelay } from '@app/relays/components/userRelay'; +import { UserRelayList } from '@app/relays/components/userRelayList'; export function RelaysScreen() { return (
-
-
-

- Connected relays -

-
- -
+
); } diff --git a/src/libs/ndk/instance.ts b/src/libs/ndk/instance.ts index 8ef8cc15..c4b6b479 100644 --- a/src/libs/ndk/instance.ts +++ b/src/libs/ndk/instance.ts @@ -72,7 +72,7 @@ export const NDKInstance = () => { enableOutboxModel: outbox, autoConnectUserRelays: true, autoFetchUserMutelist: true, - clientName: 'Lume', + // clientName: 'Lume', // clientNip89: '', }); diff --git a/src/utils/hooks/useRelay.ts b/src/utils/hooks/useRelay.ts new file mode 100644 index 00000000..e95e1755 --- /dev/null +++ b/src/utils/hooks/useRelay.ts @@ -0,0 +1,89 @@ +import { NDKEvent, NDKKind, NDKRelayUrl, NDKTag } from '@nostr-dev-kit/ndk'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { useNDK } from '@libs/ndk/provider'; +import { useStorage } from '@libs/storage/provider'; + +export function useRelay() { + const { db } = useStorage(); + const { ndk } = useNDK(); + + const queryClient = useQueryClient(); + + const connectRelay = useMutation({ + mutationFn: async (relay: NDKRelayUrl, purpose?: 'read' | 'write' | undefined) => { + // Cancel any outgoing refetches + await queryClient.cancelQueries({ queryKey: ['relays', db.account.pubkey] }); + + // Snapshot the previous value + const prevRelays: NDKTag[] = queryClient.getQueryData([ + 'relays', + db.account.pubkey, + ]); + + // create new relay list if not exist + if (!prevRelays) { + const newListEvent = new NDKEvent(ndk); + newListEvent.kind = NDKKind.RelayList; + newListEvent.tags = [['r', relay, purpose ?? '']]; + await newListEvent.publish(); + } + + // add relay to exist list + const index = prevRelays.findIndex((el) => el[1] === relay); + if (index > -1) return; + + const event = new NDKEvent(ndk); + event.kind = NDKKind.RelayList; + event.tags = [...prevRelays, ['r', relay, purpose ?? '']]; + + await event.publish(); + + // Optimistically update to the new value + queryClient.setQueryData(['relays', db.account.pubkey], (prev: NDKTag[]) => [ + ...prev, + ['r', relay, purpose ?? ''], + ]); + + // Return a context object with the snapshotted value + return { prevRelays }; + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['relays', db.account.pubkey] }); + }, + }); + + const removeRelay = useMutation({ + mutationFn: async (relay: NDKRelayUrl) => { + // Cancel any outgoing refetches + await queryClient.cancelQueries({ queryKey: ['relays', db.account.pubkey] }); + + // Snapshot the previous value + const prevRelays: NDKTag[] = queryClient.getQueryData([ + 'relays', + db.account.pubkey, + ]); + + if (!prevRelays) return; + + const index = prevRelays.findIndex((el) => el[1] === relay); + if (index > -1) prevRelays.splice(index, 1); + + const event = new NDKEvent(ndk); + event.kind = NDKKind.RelayList; + event.tags = prevRelays; + await event.publish(); + + // Optimistically update to the new value + queryClient.setQueryData(['relays', db.account.pubkey], prevRelays); + + // Return a context object with the snapshotted value + return { prevRelays }; + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['relays', db.account.pubkey] }); + }, + }); + + return { connectRelay, removeRelay }; +}