2024-10-14 08:24:43 +00:00
|
|
|
import NDK, {
|
|
|
|
getRelayListForUser,
|
|
|
|
NDKEvent,
|
|
|
|
NDKFilter,
|
|
|
|
NDKKind,
|
|
|
|
NDKRelaySet,
|
2024-10-14 14:20:43 +00:00
|
|
|
NDKSubscriptionCacheUsage,
|
|
|
|
NDKUser
|
2024-10-14 08:24:43 +00:00
|
|
|
} from '@nostr-dev-kit/ndk'
|
|
|
|
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
|
|
|
|
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from 'constants.ts'
|
2024-10-14 14:20:43 +00:00
|
|
|
import { UserRelaysType } from 'controllers'
|
2024-10-14 08:24:43 +00:00
|
|
|
import { Dexie } from 'dexie'
|
|
|
|
import { createContext, ReactNode, useEffect, useMemo } from 'react'
|
|
|
|
import { toast } from 'react-toastify'
|
2024-10-14 14:20:43 +00:00
|
|
|
import { ModDetails, UserProfile } from 'types'
|
2024-10-14 08:24:43 +00:00
|
|
|
import {
|
|
|
|
constructModListFromEvents,
|
2024-10-14 14:20:43 +00:00
|
|
|
hexToNpub,
|
2024-10-14 08:24:43 +00:00
|
|
|
log,
|
|
|
|
LogType,
|
|
|
|
npubToHex,
|
|
|
|
orderEventsChronologically
|
|
|
|
} from 'utils'
|
|
|
|
|
|
|
|
type FetchModsOptions = {
|
|
|
|
source?: string
|
|
|
|
until?: number
|
|
|
|
since?: number
|
|
|
|
limit?: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface NDKContextType {
|
|
|
|
ndk: NDK
|
|
|
|
fetchMods: (opts: FetchModsOptions) => Promise<ModDetails[]>
|
|
|
|
fetchEvents: (filter: NDKFilter, relayUrls?: string[]) => Promise<NDKEvent[]>
|
2024-10-14 14:20:43 +00:00
|
|
|
fetchEvent: (
|
|
|
|
filter: NDKFilter,
|
|
|
|
relayUrls?: string[]
|
|
|
|
) => Promise<NDKEvent | null>
|
|
|
|
|
|
|
|
fetchEventsFromUserRelays: (
|
|
|
|
filter: NDKFilter,
|
|
|
|
hexKey: string,
|
|
|
|
userRelaysType: UserRelaysType
|
|
|
|
) => Promise<NDKEvent[]>
|
|
|
|
fetchEventFromUserRelays: (
|
|
|
|
filter: NDKFilter,
|
|
|
|
hexKey: string,
|
|
|
|
userRelaysType: UserRelaysType
|
|
|
|
) => Promise<NDKEvent | null>
|
|
|
|
findMetadata: (pubkey: string) => Promise<UserProfile>
|
2024-10-14 08:24:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create the context with an initial value of `null`
|
|
|
|
export const NDKContext = createContext<NDKContextType | null>(null)
|
|
|
|
|
|
|
|
// Create a provider component to wrap around parts of your app
|
|
|
|
export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|
|
|
useEffect(() => {
|
|
|
|
window.onunhandledrejection = async (event: PromiseRejectionEvent) => {
|
|
|
|
event.preventDefault()
|
|
|
|
console.log(event.reason)
|
|
|
|
if (event.reason?.name === Dexie.errnames.DatabaseClosed) {
|
|
|
|
console.log(
|
|
|
|
'Could not open Dexie DB, probably version change. Deleting old DB and reloading...'
|
|
|
|
)
|
|
|
|
await Dexie.delete('degmod-db')
|
|
|
|
// Must reload to open a brand new DB
|
|
|
|
window.location.reload()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
const ndk = useMemo(() => {
|
|
|
|
localStorage.setItem('debug', '*')
|
|
|
|
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'degmod-db' })
|
2024-10-14 14:20:43 +00:00
|
|
|
dexieAdapter.locking = true
|
2024-10-14 08:24:43 +00:00
|
|
|
const ndk = new NDK({
|
|
|
|
enableOutboxModel: true,
|
|
|
|
autoConnectUserRelays: true,
|
|
|
|
autoFetchUserMutelist: true,
|
|
|
|
explicitRelayUrls: [
|
|
|
|
'wss://user.kindpag.es',
|
|
|
|
'wss://purplepag.es',
|
|
|
|
'wss://relay.damus.io/',
|
|
|
|
import.meta.env.VITE_APP_RELAY
|
|
|
|
],
|
|
|
|
cacheAdapter: dexieAdapter
|
|
|
|
})
|
|
|
|
|
|
|
|
ndk.connect()
|
|
|
|
|
|
|
|
return ndk
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches a list of mods based on the provided source.
|
|
|
|
*
|
|
|
|
* @param source - The source URL to filter the mods. If it matches the current window location,
|
|
|
|
* it adds a filter condition to the request.
|
|
|
|
* @param until - Optional timestamp to filter events until this time.
|
|
|
|
* @param since - Optional timestamp to filter events from this time.
|
|
|
|
* @returns A promise that resolves to an array of `ModDetails` objects. In case of an error,
|
|
|
|
* it logs the error and shows a notification, then returns an empty array.
|
|
|
|
*/
|
|
|
|
const fetchMods = async ({
|
|
|
|
source,
|
|
|
|
until,
|
|
|
|
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
|
|
|
|
limit: limit || MOD_FILTER_LIMIT, // Limit the number of events fetched to 20
|
|
|
|
'#t': [T_TAG_VALUE],
|
|
|
|
until, // Optional filter to fetch events until this timestamp
|
|
|
|
since // Optional filter to fetch events from this timestamp
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the source matches the current window location, add a filter condition
|
|
|
|
if (source === window.location.host) {
|
|
|
|
filter['#r'] = [window.location.host] // Add a tag filter for the current host
|
|
|
|
}
|
|
|
|
|
|
|
|
return ndk
|
|
|
|
.fetchEvents(
|
|
|
|
filter,
|
|
|
|
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
|
|
|
|
NDKRelaySet.fromRelayUrls(Array.from(relays), ndk, true)
|
|
|
|
)
|
|
|
|
.then((ndkEventSet) => {
|
|
|
|
const ndkEvents = Array.from(ndkEventSet)
|
|
|
|
orderEventsChronologically(ndkEvents)
|
|
|
|
|
|
|
|
// Convert the fetched events into a list of mods
|
|
|
|
const modList = constructModListFromEvents(ndkEvents)
|
|
|
|
return modList // Return the list of mods
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
// Log the error and show a notification if fetching fails
|
|
|
|
log(
|
|
|
|
true,
|
|
|
|
LogType.Error,
|
|
|
|
'An error occurred in fetching mods from relays',
|
|
|
|
err
|
|
|
|
)
|
|
|
|
toast.error('An error occurred in fetching mods from relays') // Show error notification
|
|
|
|
return [] // Return an empty array in case of an error
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-10-14 14:20:43 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @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.
|
|
|
|
*/
|
2024-10-14 08:24:43 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
return ndk
|
|
|
|
.fetchEvents(
|
|
|
|
filter,
|
|
|
|
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
|
|
|
|
NDKRelaySet.fromRelayUrls(Array.from(relays), ndk, true)
|
|
|
|
)
|
|
|
|
.then((ndkEventSet) => {
|
|
|
|
const ndkEvents = Array.from(ndkEventSet)
|
|
|
|
return orderEventsChronologically(ndkEvents)
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
// Log the error and show a notification if fetching fails
|
2024-10-14 14:20:43 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @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)
|
|
|
|
if (events.length === 0) return null
|
|
|
|
return events[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asynchronously retrieves multiple events from the user's relays based on a specified filter.
|
|
|
|
* The function first retrieves the user's relays, and then fetches the events using the provided filter.
|
|
|
|
*
|
|
|
|
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
|
|
|
* @param hexKey - The hexadecimal representation of the user's public key.
|
|
|
|
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
|
|
|
* @returns A promise that resolves with an array of events.
|
|
|
|
*/
|
|
|
|
const fetchEventsFromUserRelays = async (
|
|
|
|
filter: NDKFilter,
|
|
|
|
hexKey: string,
|
|
|
|
userRelaysType: UserRelaysType
|
|
|
|
) => {
|
|
|
|
// Find the user's relays.
|
|
|
|
const relayUrls = await getRelayListForUser(hexKey, ndk)
|
|
|
|
.then((ndkRelayList) => {
|
|
|
|
if (ndkRelayList) return ndkRelayList[userRelaysType]
|
|
|
|
return [] // Return an empty array if ndkRelayList is undefined
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
2024-10-14 08:24:43 +00:00
|
|
|
log(
|
|
|
|
true,
|
|
|
|
LogType.Error,
|
2024-10-14 14:20:43 +00:00
|
|
|
`An error occurred in fetching user's (${hexKey}) ${userRelaysType}`,
|
2024-10-14 08:24:43 +00:00
|
|
|
err
|
|
|
|
)
|
2024-10-14 14:20:43 +00:00
|
|
|
return [] as string[]
|
2024-10-14 08:24:43 +00:00
|
|
|
})
|
2024-10-14 14:20:43 +00:00
|
|
|
|
|
|
|
// Fetch the event from the user's relays using the provided filter and relay URLs
|
|
|
|
return fetchEvents(filter, relayUrls)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetches an event from the user's relays based on a specified filter.
|
|
|
|
* The function first retrieves the user's relays, and then fetches the event using the provided filter.
|
|
|
|
*
|
|
|
|
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
|
|
|
* @param hexKey - The hexadecimal representation of the user's public key.
|
|
|
|
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
|
|
|
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
|
|
|
*/
|
|
|
|
const fetchEventFromUserRelays = async (
|
|
|
|
filter: NDKFilter,
|
|
|
|
hexKey: string,
|
|
|
|
userRelaysType: UserRelaysType
|
|
|
|
) => {
|
|
|
|
const events = await fetchEventsFromUserRelays(
|
|
|
|
filter,
|
|
|
|
hexKey,
|
|
|
|
userRelaysType
|
|
|
|
)
|
|
|
|
if (events.length === 0) return null
|
|
|
|
return events[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds metadata for a given pubkey.
|
|
|
|
*
|
|
|
|
* @param hexKey - The pubkey to search for metadata.
|
|
|
|
* @returns A promise that resolves to the metadata event.
|
|
|
|
*/
|
|
|
|
const findMetadata = async (pubkey: string): Promise<UserProfile> => {
|
|
|
|
const npub = hexToNpub(pubkey)
|
|
|
|
|
|
|
|
const user = new NDKUser({ npub })
|
|
|
|
user.ndk = ndk
|
|
|
|
|
|
|
|
const userProfile = await user.fetchProfile()
|
|
|
|
|
|
|
|
return userProfile
|
2024-10-14 08:24:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2024-10-14 14:20:43 +00:00
|
|
|
<NDKContext.Provider
|
|
|
|
value={{
|
|
|
|
ndk,
|
|
|
|
fetchMods,
|
|
|
|
fetchEvents,
|
|
|
|
fetchEvent,
|
|
|
|
fetchEventsFromUserRelays,
|
|
|
|
fetchEventFromUserRelays,
|
|
|
|
findMetadata
|
|
|
|
}}
|
|
|
|
>
|
2024-10-14 08:24:43 +00:00
|
|
|
{children}
|
|
|
|
</NDKContext.Provider>
|
|
|
|
)
|
|
|
|
}
|