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
3 changed files with 58 additions and 160 deletions
Showing only changes of commit 59f4fd6b29 - Show all commits

View File

@ -33,12 +33,8 @@ type FetchModsOptions = {
interface NDKContextType {
ndk: NDK
fetchMods: (opts: FetchModsOptions) => Promise<ModDetails[]>
fetchEvents: (filter: NDKFilter, relayUrls?: string[]) => Promise<NDKEvent[]>
fetchEvent: (
filter: NDKFilter,
relayUrls?: string[]
) => Promise<NDKEvent | null>
fetchEvents: (filter: NDKFilter) => Promise<NDKEvent[]>
fetchEvent: (filter: NDKFilter) => Promise<NDKEvent | null>
fetchEventsFromUserRelays: (
filter: NDKFilter,
hexKey: string,
@ -72,6 +68,31 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
}
}, [])
const addAdminRelays = async (ndk: NDK) => {
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
adminNpubs.forEach((npub) => {
const hexKey = npubToHex(npub)
if (hexKey) {
getRelayListForUser(hexKey, ndk)
.then((ndkRelayList) => {
if (ndkRelayList) {
ndkRelayList.bothRelayUrls.forEach((url) =>
ndk.addExplicitRelay(url)
)
}
})
.catch((err) => {
log(
true,
LogType.Error,
`❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
err
)
})
}
})
}
const ndk = useMemo(() => {
localStorage.setItem('debug', '*')
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'degmod-db' })
@ -88,6 +109,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
],
cacheAdapter: dexieAdapter
})
addAdminRelays(ndk)
ndk.connect()
@ -110,33 +132,6 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
since,
limit
}: FetchModsOptions): Promise<ModDetails[]> => {
const relays = new Set<string>()
relays.add(import.meta.env.VITE_APP_RELAY)
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
const promises = adminNpubs.map((npub) => {
const hexKey = npubToHex(npub)
if (!hexKey) return null
return getRelayListForUser(hexKey, ndk)
.then((ndkRelayList) => {
if (ndkRelayList) {
ndkRelayList.writeRelayUrls.forEach((url) => relays.add(url))
}
})
.catch((err) => {
log(
true,
LogType.Error,
`❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
err
)
})
})
await Promise.allSettled(promises)
// Define the filter criteria for fetching mods
const filter: NDKFilter = {
kinds: [NDKKind.Classified], // Specify the kind of events to fetch
@ -152,11 +147,10 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
}
return ndk
.fetchEvents(
filter,
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
NDKRelaySet.fromRelayUrls(Array.from(relays), ndk, true)
)
.fetchEvents(filter, {
closeOnEose: true,
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
})
.then((ndkEventSet) => {
const ndkEvents = Array.from(ndkEventSet)
orderEventsChronologically(ndkEvents)
@ -179,56 +173,17 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
}
/**
* Asynchronously retrieves multiple event from a set of relays based on a provided filter.
* If no relays are specified, it defaults to using connected relays.
* Asynchronously retrieves multiple event based on a provided filter.
*
* @param filter - The filter criteria to find the event.
* @param relays - An optional array of relay URLs to search for the event.
* @returns Returns a promise that resolves to the found event or null if not found.
*/
const fetchEvents = async (
filter: NDKFilter,
relayUrls: string[] = []
): Promise<NDKEvent[]> => {
const relays = new Set<string>()
// add all the relays passed to relay set
relayUrls.forEach((relayUrl) => {
relays.add(relayUrl)
})
relays.add(import.meta.env.VITE_APP_RELAY)
const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
const promises = adminNpubs.map((npub) => {
const hexKey = npubToHex(npub)
if (!hexKey) return null
return getRelayListForUser(hexKey, ndk)
.then((ndkRelayList) => {
if (ndkRelayList) {
ndkRelayList.writeRelayUrls.forEach((url) => relays.add(url))
}
})
.catch((err) => {
log(
true,
LogType.Error,
`❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
err
)
})
})
await Promise.allSettled(promises)
const fetchEvents = async (filter: NDKFilter): Promise<NDKEvent[]> => {
return ndk
.fetchEvents(
filter,
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
NDKRelaySet.fromRelayUrls(Array.from(relays), ndk, true)
)
.fetchEvents(filter, {
closeOnEose: true,
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
})
.then((ndkEventSet) => {
const ndkEvents = Array.from(ndkEventSet)
return orderEventsChronologically(ndkEvents)
@ -242,15 +197,13 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
}
/**
* Asynchronously retrieves an event from a set of relays based on a provided filter.
* If no relays are specified, it defaults to using connected relays.
* Asynchronously retrieves an event based on a provided filter.
*
* @param filter - The filter criteria to find the event.
* @param relaysUrls - An optional array of relay URLs to search for the event.
* @returns Returns a promise that resolves to the found event or null if not found.
*/
const fetchEvent = async (filter: NDKFilter, relayUrls: string[] = []) => {
const events = await fetchEvents(filter, relayUrls)
const fetchEvent = async (filter: NDKFilter) => {
const events = await fetchEvents(filter)
if (events.length === 0) return null
return events[0]
}
@ -285,8 +238,22 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
return [] as string[]
})
// Fetch the event from the user's relays using the provided filter and relay URLs
return fetchEvents(filter, relayUrls)
return ndk
.fetchEvents(
filter,
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
NDKRelaySet.fromRelayUrls(relayUrls, ndk, true)
)
.then((ndkEventSet) => {
const ndkEvents = Array.from(ndkEventSet)
return orderEventsChronologically(ndkEvents)
})
.catch((err) => {
// Log the error and show a notification if fetching fails
log(true, LogType.Error, 'An error occurred in fetching events', err)
toast.error('An error occurred in fetching events') // Show error notification
return [] // Return an empty array in case of an error
})
}
/**

View File

@ -372,75 +372,6 @@ export class RelayController {
return publishedOnRelays
}
/**
* Subscribes to events from multiple relays.
*
* This method connects to the specified relay URLs and subscribes to events
* using the provided filter. It handles incoming events through the given
* `eventHandler` callback and manages the subscription lifecycle.
*
* @param filter - The filter criteria to apply when subscribing to events.
* @param relayUrls - An optional array of relay URLs to connect to. The default relay URL (`APP_RELAY`) is added automatically.
* @param eventHandler - A callback function to handle incoming events. It receives an `Event` object.
*
*/
subscribeForEvents = async (
filter: Filter,
relayUrls: string[] = [],
eventHandler: (event: Event) => void
) => {
const appRelay = import.meta.env.VITE_APP_RELAY
if (!relayUrls.includes(appRelay)) {
/**
* NOTE: To avoid side-effects on external relayUrls array passed as argument
* re-assigned relayUrls with added sigit relay instead of just appending to same array
*/
relayUrls = [...relayUrls, appRelay] // Add app relay to relays array if not exists already
}
// 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
}, [])
// Check if any relays are connected
if (relays.length === 0) {
throw new Error('No relay is connected to fetch events!')
}
const processedEvents: string[] = [] // To keep track of processed events
// Create a promise for each relay subscription
const subscriptions = relays.map((relay) =>
relay.subscribe([filter], {
// Handle incoming events
onevent: (e) => {
// Process event only if it hasn't been processed before
if (!processedEvents.includes(e.id)) {
processedEvents.push(e.id)
eventHandler(e) // Call the event handler with the event
}
}
})
)
return subscriptions
}
getTotalZapAmount = async (
user: string,
eTag: string,

View File

@ -392,7 +392,7 @@ const UsersResult = ({
}
setIsFetching(true)
fetchEvents(filter, ['wss://purplepag.es', 'wss://user.kindpag.es'])
fetchEvents(filter)
.then((events) => {
const results = events.map((event) => {
const ndkEvent = new NDKEvent(undefined, event)