degmods.com/src/contexts/NDKContext.tsx

227 lines
6.8 KiB
TypeScript
Raw Normal View History

2024-10-14 08:24:43 +00:00
import NDK, {
getRelayListForUser,
NDKEvent,
NDKFilter,
NDKKind,
NDKRelaySet,
NDKSubscriptionCacheUsage
} from '@nostr-dev-kit/ndk'
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from 'constants.ts'
import { Dexie } from 'dexie'
import { createContext, ReactNode, useEffect, useMemo } from 'react'
import { toast } from 'react-toastify'
import { ModDetails } from 'types'
import {
constructModListFromEvents,
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[]>
}
// 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' })
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
})
}
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
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
})
}
return (
<NDKContext.Provider value={{ ndk, fetchMods, fetchEvents }}>
{children}
</NDKContext.Provider>
)
}