From 9dec5201ba84da94afd0220bfe118833cc7d8986 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 7 Dec 2024 13:51:14 +0800 Subject: [PATCH] feat: optimize for algo relays --- .../src/components/NoteList/index.tsx | 36 +++++++++----- .../src/components/RelaySettings/RelayUrl.tsx | 4 +- .../RelaySettings/TemporaryRelayGroup.tsx | 2 +- src/renderer/src/hooks/useFetchRelayInfos.tsx | 13 ++++- src/renderer/src/lib/relay.ts | 5 ++ src/renderer/src/providers/NostrProvider.tsx | 6 +-- .../src/providers/RelaySettingsProvider.tsx | 19 ++++--- src/renderer/src/services/client.service.ts | 49 +++++++++++-------- src/renderer/src/services/storage.service.ts | 7 +-- src/renderer/src/types.ts | 1 + 10 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 src/renderer/src/lib/relay.ts diff --git a/src/renderer/src/components/NoteList/index.tsx b/src/renderer/src/components/NoteList/index.tsx index a1a2cd7..f98a709 100644 --- a/src/renderer/src/components/NoteList/index.tsx +++ b/src/renderer/src/components/NoteList/index.tsx @@ -1,4 +1,5 @@ import { Button } from '@renderer/components/ui/button' +import { useFetchRelayInfos } from '@renderer/hooks' import { isReplyNoteEvent } from '@renderer/lib/event' import { cn } from '@renderer/lib/utils' import { useNostr } from '@renderer/providers/NostrProvider' @@ -6,8 +7,8 @@ import client from '@renderer/services/client.service' import dayjs from 'dayjs' import { Event, Filter, kinds } from 'nostr-tools' import { useEffect, useMemo, useRef, useState } from 'react' -import NoteCard from '../NoteCard' import { useTranslation } from 'react-i18next' +import NoteCard from '../NoteCard' export default function NoteList({ relayUrls, @@ -19,7 +20,8 @@ export default function NoteList({ className?: string }) { const { t } = useTranslation() - const { isReady, singEvent } = useNostr() + const { isReady, signEvent } = useNostr() + const { isFetching: isFetchingRelayInfo, areAlgoRelays } = useFetchRelayInfos(relayUrls) const [events, setEvents] = useState([]) const [newEvents, setNewEvents] = useState([]) const [until, setUntil] = useState(() => dayjs().unix()) @@ -30,13 +32,13 @@ export default function NoteList({ const noteFilter = useMemo(() => { return { kinds: [kinds.ShortTextNote, kinds.Repost], - limit: 200, + limit: areAlgoRelays ? 500 : 200, ...filter } - }, [JSON.stringify(filter)]) + }, [JSON.stringify(filter), areAlgoRelays]) useEffect(() => { - if (!isReady) return + if (!isReady || isFetchingRelayInfo) return setInitialized(false) setEvents([]) @@ -48,6 +50,9 @@ export default function NoteList({ noteFilter, { onEose: (events) => { + if (!areAlgoRelays) { + events.sort((a, b) => b.created_at - a.created_at) + } const processedEvents = events.filter((e) => !isReplyNoteEvent(e)) if (processedEvents.length > 0) { setEvents((pre) => [...pre, ...processedEvents]) @@ -55,6 +60,9 @@ export default function NoteList({ if (events.length > 0) { setUntil(events[events.length - 1].created_at - 1) } + if (areAlgoRelays) { + setHasMore(false) + } setInitialized(true) }, onNew: (event) => { @@ -63,13 +71,19 @@ export default function NoteList({ } } }, - singEvent + signEvent ) return () => { subCloser() } - }, [JSON.stringify(relayUrls), JSON.stringify(noteFilter), isReady]) + }, [ + JSON.stringify(relayUrls), + JSON.stringify(noteFilter), + isReady, + isFetchingRelayInfo, + areAlgoRelays + ]) useEffect(() => { if (!initialized) return @@ -119,9 +133,9 @@ export default function NoteList({ } return ( - <> +
{newEvents.length > 0 && ( -
+
@@ -132,9 +146,9 @@ export default function NoteList({ ))}
-
+
{hasMore ?
{t('loading...')}
: t('no more notes')}
- +
) } diff --git a/src/renderer/src/components/RelaySettings/RelayUrl.tsx b/src/renderer/src/components/RelaySettings/RelayUrl.tsx index 1338540..3588466 100644 --- a/src/renderer/src/components/RelaySettings/RelayUrl.tsx +++ b/src/renderer/src/components/RelaySettings/RelayUrl.tsx @@ -113,7 +113,9 @@ function RelayUrl({ onRemove: () => void }) { const { t } = useTranslation() - const [relayInfo] = useFetchRelayInfos([url]) + const { + relayInfos: [relayInfo] + } = useFetchRelayInfos([url]) return (
diff --git a/src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx b/src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx index 8afe868..21eb7b6 100644 --- a/src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx +++ b/src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx @@ -15,7 +15,7 @@ export default function TemporaryRelayGroup() { isConnected: boolean }[] >(temporaryRelayUrls.map((url) => ({ url, isConnected: false }))) - const relayInfos = useFetchRelayInfos(relays.map((relay) => relay.url)) + const { relayInfos } = useFetchRelayInfos(relays.map((relay) => relay.url)) useEffect(() => { const interval = setInterval(() => { diff --git a/src/renderer/src/hooks/useFetchRelayInfos.tsx b/src/renderer/src/hooks/useFetchRelayInfos.tsx index ce00149..a7a2b9b 100644 --- a/src/renderer/src/hooks/useFetchRelayInfos.tsx +++ b/src/renderer/src/hooks/useFetchRelayInfos.tsx @@ -1,23 +1,32 @@ +import { checkIfAlgoRelay } from '@renderer/lib/relay' import client from '@renderer/services/client.service' import { TRelayInfo } from '@renderer/types' import { useEffect, useState } from 'react' export function useFetchRelayInfos(urls: string[]) { + const [isFetching, setIsFetching] = useState(true) const [relayInfos, setRelayInfos] = useState<(TRelayInfo | undefined)[]>([]) + const [areAlgoRelays, setAreAlgoRelays] = useState(false) useEffect(() => { const fetchRelayInfos = async () => { - if (urls.length === 0) return + setIsFetching(true) + if (urls.length === 0) { + return setIsFetching(false) + } try { const relayInfos = await client.fetchRelayInfos(urls) setRelayInfos(relayInfos) + setAreAlgoRelays(relayInfos.every((relayInfo) => checkIfAlgoRelay(relayInfo))) } catch (err) { console.error(err) + } finally { + setIsFetching(false) } } fetchRelayInfos() }, [JSON.stringify(urls)]) - return relayInfos + return { relayInfos, isFetching, areAlgoRelays } } diff --git a/src/renderer/src/lib/relay.ts b/src/renderer/src/lib/relay.ts new file mode 100644 index 0000000..e49e9b3 --- /dev/null +++ b/src/renderer/src/lib/relay.ts @@ -0,0 +1,5 @@ +import { TRelayInfo } from '@renderer/types' + +export function checkIfAlgoRelay(relayInfo: TRelayInfo | undefined) { + return relayInfo?.software === 'https://github.com/bitvora/algo-relay' // hardcode for now +} diff --git a/src/renderer/src/providers/NostrProvider.tsx b/src/renderer/src/providers/NostrProvider.tsx index 1bb29ac..3517afe 100644 --- a/src/renderer/src/providers/NostrProvider.tsx +++ b/src/renderer/src/providers/NostrProvider.tsx @@ -21,7 +21,7 @@ type TNostrContext = { */ publish: (draftEvent: TDraftEvent, additionalRelayUrls?: string[]) => Promise signHttpAuth: (url: string, method: string) => Promise - singEvent: (draftEvent: TDraftEvent) => Promise + signEvent: (draftEvent: TDraftEvent) => Promise checkLogin: (cb?: () => void | Promise) => void } @@ -117,7 +117,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { return event } - const singEvent = async (draftEvent: TDraftEvent) => { + const signEvent = async (draftEvent: TDraftEvent) => { const event = await window.nostr?.signEvent(draftEvent) if (!event) { throw new Error('sign event failed') @@ -173,7 +173,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { publish, signHttpAuth, checkLogin, - singEvent + signEvent }} > {children} diff --git a/src/renderer/src/providers/RelaySettingsProvider.tsx b/src/renderer/src/providers/RelaySettingsProvider.tsx index ec8c7f8..b107ba5 100644 --- a/src/renderer/src/providers/RelaySettingsProvider.tsx +++ b/src/renderer/src/providers/RelaySettingsProvider.tsx @@ -1,4 +1,5 @@ import { TRelayGroup } from '@common/types' +import { checkIfAlgoRelay } from '@renderer/lib/relay' import { isWebsocketUrl, normalizeUrl } from '@renderer/lib/url' import client from '@renderer/services/client.service' import storage from '@renderer/services/storage.service' @@ -9,6 +10,7 @@ type TRelaySettingsContext = { temporaryRelayUrls: string[] relayUrls: string[] searchableRelayUrls: string[] + areAlgoRelays: boolean switchRelayGroup: (groupName: string) => void renameRelayGroup: (oldGroupName: string, newGroupName: string) => string | null deleteRelayGroup: (groupName: string) => void @@ -36,6 +38,7 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode : (relayGroups.find((group) => group.isActive)?.relayUrls ?? []) ) const [searchableRelayUrls, setSearchableRelayUrls] = useState([]) + const [areAlgoRelays, setAreAlgoRelays] = useState(false) useEffect(() => { const init = async () => { @@ -55,23 +58,24 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode }, []) useEffect(() => { - setRelayUrls( - temporaryRelayUrls.length + const handler = async () => { + const relayUrls = temporaryRelayUrls.length ? temporaryRelayUrls : (relayGroups.find((group) => group.isActive)?.relayUrls ?? []) - ) - }, [relayGroups, temporaryRelayUrls]) - useEffect(() => { - const handler = async () => { setSearchableRelayUrls([]) + setRelayUrls([]) const relayInfos = await client.fetchRelayInfos(relayUrls) setSearchableRelayUrls( relayUrls.filter((_, index) => relayInfos[index]?.supported_nips?.includes(50)) ) + const nonAlgoRelayUrls = relayUrls.filter((_, index) => !checkIfAlgoRelay(relayInfos[index])) + setAreAlgoRelays(relayUrls.length > 0 && nonAlgoRelayUrls.length === 0) + setRelayUrls(relayUrls) + client.setCurrentRelayUrls(nonAlgoRelayUrls) } handler() - }, [relayUrls]) + }, [relayGroups, temporaryRelayUrls]) const updateGroups = async (fn: (pre: TRelayGroup[]) => TRelayGroup[]) => { let newGroups = relayGroups @@ -154,6 +158,7 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode temporaryRelayUrls, relayUrls, searchableRelayUrls, + areAlgoRelays, switchRelayGroup, renameRelayGroup, deleteRelayGroup, diff --git a/src/renderer/src/services/client.service.ts b/src/renderer/src/services/client.service.ts index eefc79b..72d471a 100644 --- a/src/renderer/src/services/client.service.ts +++ b/src/renderer/src/services/client.service.ts @@ -26,6 +26,7 @@ const BIG_RELAY_URLS = [ class ClientService { static instance: ClientService + private defaultRelayUrls: string[] = BIG_RELAY_URLS private pool = new SimplePool() private eventCache = new LRUCache>({ max: 10000 }) @@ -33,7 +34,7 @@ class ClientService { (ids) => Promise.all(ids.map((id) => this._fetchEvent(id))), { cacheMap: this.eventCache } ) - private fetchEventFromBigRelaysDataloader = new DataLoader( + private fetchEventFromDefaultRelaysDataloader = new DataLoader( this.eventBatchLoadFn.bind(this), { cache: false } ) @@ -45,7 +46,7 @@ class ClientService { (ids) => Promise.all(ids.map((id) => this._fetchProfile(id))), { cacheMap: this.profileCache } ) - private fetchProfileFromBigRelaysDataloader = new DataLoader( + private fetchProfileFromDefaultRelaysDataloader = new DataLoader( this.profileBatchLoadFn.bind(this), { cache: false } ) @@ -85,6 +86,10 @@ class ClientService { return this.pool.listConnectionStatus() } + setCurrentRelayUrls(urls: string[]) { + this.defaultRelayUrls = Array.from(new Set(urls.concat(BIG_RELAY_URLS))) + } + async publishEvent(relayUrls: string[], event: NEvent) { return await Promise.any(this.pool.publish(relayUrls, event)) } @@ -146,7 +151,6 @@ class ClientService { oneose() { eosed++ if (eosed === started) { - events.sort((a, b) => b.created_at - a.created_at) onEose(events) } } @@ -196,7 +200,7 @@ class ClientService { const events: NEvent[] = [] let hasEosed = false const closer = this.pool.subscribeMany( - relayUrls.length > 0 ? relayUrls : BIG_RELAY_URLS, + relayUrls.length > 0 ? relayUrls : this.defaultRelayUrls, [ { '#e': [parentEventId], @@ -280,7 +284,7 @@ class ClientService { async fetchEvents(relayUrls: string[], filter: Filter, cache = false) { const events = await this.pool.querySync( - relayUrls.length > 0 ? relayUrls : BIG_RELAY_URLS, + relayUrls.length > 0 ? relayUrls : this.defaultRelayUrls, filter ) if (cache) { @@ -374,7 +378,7 @@ class ClientService { } private async fetchEventById(relayUrls: string[], id: string): Promise { - const event = await this.fetchEventFromBigRelaysDataloader.load(id) + const event = await this.fetchEventFromDefaultRelaysDataloader.load(id) if (event) { return event } @@ -449,9 +453,9 @@ class ClientService { throw new Error('Invalid id') } - const profileFromBigRelays = await this.fetchProfileFromBigRelaysDataloader.load(pubkey) - if (profileFromBigRelays) { - return profileFromBigRelays + const profileFromDefaultRelays = await this.fetchProfileFromDefaultRelaysDataloader.load(pubkey) + if (profileFromDefaultRelays) { + return profileFromDefaultRelays } const profileEvent = await this.tryHarderToFetchEvent( @@ -477,15 +481,15 @@ class ClientService { private async tryHarderToFetchEvent( relayUrls: string[], filter: Filter, - alreadyFetchedFromBigRelays = false + alreadyFetchedFromDefaultRelays = false ) { if (!relayUrls.length && filter.authors?.length) { const relayList = await this.fetchRelayList(filter.authors[0]) - relayUrls = alreadyFetchedFromBigRelays - ? relayList.write.filter((url) => !BIG_RELAY_URLS.includes(url)).slice(0, 4) + relayUrls = alreadyFetchedFromDefaultRelays + ? relayList.write.filter((url) => !this.defaultRelayUrls.includes(url)).slice(0, 4) : relayList.write.slice(0, 4) - } else if (!relayUrls.length && !alreadyFetchedFromBigRelays) { - relayUrls = BIG_RELAY_URLS + } else if (!relayUrls.length && !alreadyFetchedFromDefaultRelays) { + relayUrls = this.defaultRelayUrls } if (!relayUrls.length) return @@ -494,7 +498,7 @@ class ClientService { } private async eventBatchLoadFn(ids: readonly string[]) { - const events = await this.pool.querySync(BIG_RELAY_URLS, { + const events = await this.pool.querySync(this.defaultRelayUrls, { ids: Array.from(new Set(ids)), limit: ids.length }) @@ -507,7 +511,7 @@ class ClientService { } private async profileBatchLoadFn(pubkeys: readonly string[]) { - const events = await this.pool.querySync(BIG_RELAY_URLS, { + const events = await this.pool.querySync(this.defaultRelayUrls, { authors: Array.from(new Set(pubkeys)), kinds: [kinds.Metadata], limit: pubkeys.length @@ -528,7 +532,7 @@ class ClientService { } private async relayListBatchLoadFn(pubkeys: readonly string[]) { - const events = await this.pool.querySync(BIG_RELAY_URLS, { + const events = await this.pool.querySync(this.defaultRelayUrls, { authors: pubkeys as string[], kinds: [kinds.RelayList], limit: pubkeys.length @@ -572,10 +576,13 @@ class ClientService { private async _fetchFollowListEvent(pubkey: string) { const relayList = await this.fetchRelayList(pubkey) - const followListEvents = await this.pool.querySync(relayList.write.concat(BIG_RELAY_URLS), { - authors: [pubkey], - kinds: [kinds.Contacts] - }) + const followListEvents = await this.pool.querySync( + relayList.write.concat(this.defaultRelayUrls), + { + authors: [pubkey], + kinds: [kinds.Contacts] + } + ) return followListEvents.sort((a, b) => b.created_at - a.created_at)[0] } diff --git a/src/renderer/src/services/storage.service.ts b/src/renderer/src/services/storage.service.ts index 1bb831c..f08f7a0 100644 --- a/src/renderer/src/services/storage.service.ts +++ b/src/renderer/src/services/storage.service.ts @@ -5,12 +5,7 @@ import { isElectron } from '@renderer/lib/env' const DEFAULT_RELAY_GROUPS: TRelayGroup[] = [ { groupName: 'Global', - relayUrls: [ - 'wss://relay.damus.io/', - 'wss://nos.lol/', - 'wss://nostr.mom/', - 'wss://relay.primal.net/' - ], + relayUrls: ['wss://relay.damus.io/', 'wss://nos.lol/'], isActive: true } ] diff --git a/src/renderer/src/types.ts b/src/renderer/src/types.ts index 8a8c0f4..8a5350e 100644 --- a/src/renderer/src/types.ts +++ b/src/renderer/src/types.ts @@ -15,6 +15,7 @@ export type TRelayList = { export type TRelayInfo = { supported_nips?: number[] + software?: string } export type TWebMetadata = {