Refactor to use NDK instead of custom relay and metadata controllers #89

Merged
enes merged 6 commits from ndk-refactor into staging 2024-10-21 13:39:56 +00:00
6 changed files with 87 additions and 176 deletions
Showing only changes of commit 82b87b3e32 - Show all commits

View File

@ -5,8 +5,7 @@ import { handleModImageError } from '../utils'
import { ModDetails } from 'types'
import { getModPageRoute } from 'routes'
import { kinds, nip19 } from 'nostr-tools'
import { useDidMount, useReactions } from 'hooks'
import { RelayController } from 'controllers'
import { useDidMount, useNDKContext, useReactions } from 'hooks'
import { toast } from 'react-toastify'
import { useComments } from 'hooks/useComments'
@ -19,10 +18,10 @@ export const ModCard = React.memo((props: ModDetails) => {
eTag: props.id,
aTag: props.aTag
})
const { getTotalZapAmount } = useNDKContext()
useDidMount(() => {
RelayController.getInstance()
.getTotalZapAmount(props.author, props.id, props.aTag)
getTotalZapAmount(props.author, props.id, props.aTag)
.then((res) => {
setTotalZappedAmount(res.accumulatedZapAmount)
})

View File

@ -5,7 +5,8 @@ import NDK, {
NDKKind,
NDKRelaySet,
NDKSubscriptionCacheUsage,
NDKUser
NDKUser,
zapInvoiceFromEvent
} from '@nostr-dev-kit/ndk'
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from 'constants.ts'
@ -36,16 +37,25 @@ interface NDKContextType {
fetchEvents: (filter: NDKFilter) => Promise<NDKEvent[]>
fetchEvent: (filter: NDKFilter) => Promise<NDKEvent | null>
fetchEventsFromUserRelays: (
filter: NDKFilter,
filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => Promise<NDKEvent[]>
fetchEventFromUserRelays: (
filter: NDKFilter,
filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => Promise<NDKEvent | null>
findMetadata: (pubkey: string) => Promise<UserProfile>
getTotalZapAmount: (
user: string,
eTag: string,
aTag?: string,
currentLoggedInUser?: string
) => Promise<{
accumulatedZapAmount: number
hasZapped: boolean
}>
}
// Create the context with an initial value of `null`
@ -218,10 +228,10 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
* @returns A promise that resolves with an array of events.
*/
const fetchEventsFromUserRelays = async (
filter: NDKFilter,
filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => {
): Promise<NDKEvent[]> => {
// Find the user's relays.
const relayUrls = await getRelayListForUser(hexKey, ndk)
.then((ndkRelayList) => {
@ -266,7 +276,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
* @returns A promise that resolves to the fetched event or null if the operation fails.
*/
const fetchEventFromUserRelays = async (
filter: NDKFilter,
filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => {
@ -296,6 +306,52 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
return userProfile
}
const getTotalZapAmount = async (
user: string,
eTag: string,
aTag?: string,
currentLoggedInUser?: string
) => {
const filters: NDKFilter[] = [
{
kinds: [NDKKind.Zap],
'#e': [eTag],
'#p': [user]
}
]
if (aTag) {
filters.push({
kinds: [NDKKind.Zap],
'#a': [aTag],
'#p': [user]
})
}
const zapEvents = await fetchEventsFromUserRelays(
filters,
user,
UserRelaysType.Read
)
let accumulatedZapAmount = 0
let hasZapped = false
zapEvents.forEach((zap) => {
const zapInvoice = zapInvoiceFromEvent(zap)
if (zapInvoice) {
accumulatedZapAmount += Math.round(zapInvoice.amount / 1000)
if (!hasZapped) hasZapped = zapInvoice.zappee === currentLoggedInUser
}
})
return {
accumulatedZapAmount,
hasZapped
}
}
return (
<NDKContext.Provider
value={{
@ -305,7 +361,8 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
fetchEvent,
fetchEventsFromUserRelays,
fetchEventFromUserRelays,
findMetadata
findMetadata,
getTotalZapAmount
}}
>
{children}

View File

@ -1,11 +1,5 @@
import { Event, Filter, kinds, nip57, Relay } from 'nostr-tools'
import {
extractZapAmount,
log,
LogType,
normalizeWebSocketURL,
timeout
} from '../utils'
import { Event, Relay } from 'nostr-tools'
import { log, LogType, normalizeWebSocketURL, timeout } from '../utils'
import { MetadataController, UserRelaysType } from './metadata'
/**
@ -371,122 +365,4 @@ export class RelayController {
// Return the list of relay URLs where the event was successfully published
return publishedOnRelays
}
getTotalZapAmount = async (
user: string,
eTag: string,
aTag?: string,
currentLoggedInUser?: string
) => {
const metadataController = await MetadataController.getInstance()
const relayUrls = await metadataController.findUserRelays(
user,
UserRelaysType.Read
)
const appRelay = import.meta.env.VITE_APP_RELAY
if (!relayUrls.includes(appRelay)) {
relayUrls.push(appRelay)
}
// Connect to all specified relays
const relayPromises = relayUrls.map((relayUrl) =>
this.connectRelay(relayUrl)
)
// Use Promise.allSettled to wait for all promises to settle
const results = await Promise.allSettled(relayPromises)
// Extract non-null values from fulfilled promises in a single pass
const relays = results.reduce<Relay[]>((acc, result) => {
if (result.status === 'fulfilled') {
const value = result.value
if (value) {
acc.push(value)
}
}
return acc
}, [])
let accumulatedZapAmount = 0
let hasZapped = false
const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates
const filters: Filter[] = [
{
kinds: [kinds.Zap],
'#e': [eTag]
}
]
if (aTag) {
filters.push({
kinds: [kinds.Zap],
'#a': [aTag]
})
}
// Create a promise for each relay subscription
const subPromises = relays.map((relay) => {
return new Promise<void>((resolve) => {
// Subscribe to the relay with the specified filter
const sub = relay.subscribe(filters, {
// Handle incoming events
onevent: (e) => {
// Add the event to the array if it's not a duplicate
if (!eventIds.has(e.id)) {
eventIds.add(e.id) // Record the event ID
const zapRequestStr = e.tags.find(
(t) => t[0] === 'description'
)?.[1]
if (!zapRequestStr) return
const error = nip57.validateZapRequest(zapRequestStr)
if (error) return
let zapRequest: Event | null = null
try {
zapRequest = JSON.parse(zapRequestStr)
} catch (error) {
log(
true,
LogType.Error,
'Error occurred in parsing zap request',
error
)
}
if (!zapRequest) return
const amount = extractZapAmount(zapRequest)
accumulatedZapAmount += amount
if (amount > 0) {
if (!hasZapped) {
hasZapped = zapRequest.pubkey === currentLoggedInUser
}
}
}
},
// Handle the End-Of-Stream (EOSE) message
oneose: () => {
sub.close() // Close the subscription
resolve() // Resolve the promise when EOSE is received
}
})
})
})
// Wait for all subscriptions to complete
await Promise.allSettled(subPromises)
return {
accumulatedZapAmount,
hasZapped
}
}
}

View File

@ -9,23 +9,5 @@ export const useNDKContext = () => {
'NDKContext should not be used in out component tree hierarchy'
)
const {
ndk,
fetchEvents,
fetchEvent,
fetchEventsFromUserRelays,
fetchEventFromUserRelays,
fetchMods,
findMetadata
} = ndkContext
return {
ndk,
fetchEvents,
fetchEvent,
fetchEventsFromUserRelays,
fetchEventFromUserRelays,
fetchMods,
findMetadata
}
return { ...ndkContext }
}

View File

@ -496,20 +496,19 @@ const Reactions = (props: Event) => {
const Zap = (props: Event) => {
const [isOpen, setIsOpen] = useState(false)
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
const [hasZapped, setHasZapped] = useState(false)
const userState = useAppSelector((state) => state.user)
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
const { getTotalZapAmount } = useNDKContext()
useDidMount(() => {
RelayController.getInstance()
.getTotalZapAmount(
props.pubkey,
props.id,
undefined,
userState.user?.pubkey as string
)
getTotalZapAmount(
props.pubkey,
props.id,
undefined,
userState.user?.pubkey as string
)
.then((res) => {
setTotalZappedAmount(res.accumulatedZapAmount)
setHasZapped(res.hasZapped)

View File

@ -1,6 +1,5 @@
import { ZapSplit } from 'components/Zap'
import { RelayController } from 'controllers'
import { useAppSelector, useDidMount } from 'hooks'
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
import { useState } from 'react'
import { toast } from 'react-toastify'
import { ModDetails } from 'types'
@ -12,20 +11,19 @@ type ZapProps = {
export const Zap = ({ modDetails }: ZapProps) => {
const [isOpen, setIsOpen] = useState(false)
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
const [hasZapped, setHasZapped] = useState(false)
const userState = useAppSelector((state) => state.user)
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
const { getTotalZapAmount } = useNDKContext()
useDidMount(() => {
RelayController.getInstance()
.getTotalZapAmount(
modDetails.author,
modDetails.id,
modDetails.aTag,
userState.user?.pubkey as string
)
getTotalZapAmount(
modDetails.author,
modDetails.id,
modDetails.aTag,
userState.user?.pubkey as string
)
.then((res) => {
setTotalZappedAmount(res.accumulatedZapAmount)
setHasZapped(res.hasZapped)