Browse Source

feat: optimize for algo relays

imwald
codytseng 1 year ago
parent
commit
9dec5201ba
  1. 36
      src/renderer/src/components/NoteList/index.tsx
  2. 4
      src/renderer/src/components/RelaySettings/RelayUrl.tsx
  3. 2
      src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx
  4. 13
      src/renderer/src/hooks/useFetchRelayInfos.tsx
  5. 5
      src/renderer/src/lib/relay.ts
  6. 6
      src/renderer/src/providers/NostrProvider.tsx
  7. 19
      src/renderer/src/providers/RelaySettingsProvider.tsx
  8. 49
      src/renderer/src/services/client.service.ts
  9. 7
      src/renderer/src/services/storage.service.ts
  10. 1
      src/renderer/src/types.ts

36
src/renderer/src/components/NoteList/index.tsx

@ -1,4 +1,5 @@ @@ -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' @@ -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({ @@ -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<Event[]>([])
const [newEvents, setNewEvents] = useState<Event[]>([])
const [until, setUntil] = useState<number>(() => dayjs().unix())
@ -30,13 +32,13 @@ export default function NoteList({ @@ -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({ @@ -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({ @@ -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({ @@ -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({ @@ -119,9 +133,9 @@ export default function NoteList({
}
return (
<>
<div className="space-y-2 sm:space-y-4">
{newEvents.length > 0 && (
<div className="flex justify-center w-full sm:mb-4 max-sm:mt-2">
<div className="flex justify-center w-full max-sm:mt-2">
<Button size="lg" onClick={showNewEvents}>
{t('show new notes')}
</Button>
@ -132,9 +146,9 @@ export default function NoteList({ @@ -132,9 +146,9 @@ export default function NoteList({
<NoteCard key={`${i}-${event.id}`} className="w-full" event={event} />
))}
</div>
<div className="text-center text-sm text-muted-foreground mt-2">
<div className="text-center text-sm text-muted-foreground">
{hasMore ? <div ref={bottomRef}>{t('loading...')}</div> : t('no more notes')}
</div>
</>
</div>
)
}

4
src/renderer/src/components/RelaySettings/RelayUrl.tsx

@ -113,7 +113,9 @@ function RelayUrl({ @@ -113,7 +113,9 @@ function RelayUrl({
onRemove: () => void
}) {
const { t } = useTranslation()
const [relayInfo] = useFetchRelayInfos([url])
const {
relayInfos: [relayInfo]
} = useFetchRelayInfos([url])
return (
<div className="flex items-center justify-between">

2
src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx

@ -15,7 +15,7 @@ export default function TemporaryRelayGroup() { @@ -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(() => {

13
src/renderer/src/hooks/useFetchRelayInfos.tsx

@ -1,23 +1,32 @@ @@ -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 }
}

5
src/renderer/src/lib/relay.ts

@ -0,0 +1,5 @@ @@ -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
}

6
src/renderer/src/providers/NostrProvider.tsx

@ -21,7 +21,7 @@ type TNostrContext = { @@ -21,7 +21,7 @@ type TNostrContext = {
*/
publish: (draftEvent: TDraftEvent, additionalRelayUrls?: string[]) => Promise<Event>
signHttpAuth: (url: string, method: string) => Promise<string>
singEvent: (draftEvent: TDraftEvent) => Promise<Event>
signEvent: (draftEvent: TDraftEvent) => Promise<Event>
checkLogin: (cb?: () => void | Promise<void>) => void
}
@ -117,7 +117,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -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 }) { @@ -173,7 +173,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
publish,
signHttpAuth,
checkLogin,
singEvent
signEvent
}}
>
{children}

19
src/renderer/src/providers/RelaySettingsProvider.tsx

@ -1,4 +1,5 @@ @@ -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 = { @@ -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 @@ -36,6 +38,7 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode
: (relayGroups.find((group) => group.isActive)?.relayUrls ?? [])
)
const [searchableRelayUrls, setSearchableRelayUrls] = useState<string[]>([])
const [areAlgoRelays, setAreAlgoRelays] = useState(false)
useEffect(() => {
const init = async () => {
@ -55,23 +58,24 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode @@ -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 @@ -154,6 +158,7 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode
temporaryRelayUrls,
relayUrls,
searchableRelayUrls,
areAlgoRelays,
switchRelayGroup,
renameRelayGroup,
deleteRelayGroup,

49
src/renderer/src/services/client.service.ts

@ -26,6 +26,7 @@ const BIG_RELAY_URLS = [ @@ -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<string, Promise<NEvent | undefined>>({ max: 10000 })
@ -33,7 +34,7 @@ class ClientService { @@ -33,7 +34,7 @@ class ClientService {
(ids) => Promise.all(ids.map((id) => this._fetchEvent(id))),
{ cacheMap: this.eventCache }
)
private fetchEventFromBigRelaysDataloader = new DataLoader<string, NEvent | undefined>(
private fetchEventFromDefaultRelaysDataloader = new DataLoader<string, NEvent | undefined>(
this.eventBatchLoadFn.bind(this),
{ cache: false }
)
@ -45,7 +46,7 @@ class ClientService { @@ -45,7 +46,7 @@ class ClientService {
(ids) => Promise.all(ids.map((id) => this._fetchProfile(id))),
{ cacheMap: this.profileCache }
)
private fetchProfileFromBigRelaysDataloader = new DataLoader<string, TProfile | undefined>(
private fetchProfileFromDefaultRelaysDataloader = new DataLoader<string, TProfile | undefined>(
this.profileBatchLoadFn.bind(this),
{ cache: false }
)
@ -85,6 +86,10 @@ class ClientService { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -374,7 +378,7 @@ class ClientService {
}
private async fetchEventById(relayUrls: string[], id: string): Promise<NEvent | undefined> {
const event = await this.fetchEventFromBigRelaysDataloader.load(id)
const event = await this.fetchEventFromDefaultRelaysDataloader.load(id)
if (event) {
return event
}
@ -449,9 +453,9 @@ class ClientService { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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]
}

7
src/renderer/src/services/storage.service.ts

@ -5,12 +5,7 @@ import { isElectron } from '@renderer/lib/env' @@ -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
}
]

1
src/renderer/src/types.ts

@ -15,6 +15,7 @@ export type TRelayList = { @@ -15,6 +15,7 @@ export type TRelayList = {
export type TRelayInfo = {
supported_nips?: number[]
software?: string
}
export type TWebMetadata = {

Loading…
Cancel
Save