sigit.io/src/contexts/NDKContext.tsx

275 lines
8.3 KiB
TypeScript
Raw Normal View History

import NDK, {
getRelayListForUser,
Hexpubkey,
NDKEvent,
NDKFilter,
NDKRelayList,
NDKRelaySet,
NDKSubscriptionCacheUsage,
NDKSubscriptionOptions,
NDKUser,
NDKUserProfile
} from '@nostr-dev-kit/ndk'
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
import { Dexie } from 'dexie'
import { createContext, ReactNode, useEffect, useMemo } from 'react'
import { toast } from 'react-toastify'
import { UserRelaysType } from '../types'
import {
DEFAULT_LOOK_UP_RELAY_LIST,
hexToNpub,
orderEventsChronologically,
SIGIT_RELAY,
timeout
} from '../utils'
export interface NDKContextType {
ndk: NDK
fetchEvents: (filter: NDKFilter) => Promise<NDKEvent[]>
fetchEvent: (filter: NDKFilter) => Promise<NDKEvent | null>
fetchEventsFromUserRelays: (
filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => Promise<NDKEvent[]>
fetchEventFromUserRelays: (
filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => Promise<NDKEvent | null>
findMetadata: (
pubkey: string,
opts?: NDKSubscriptionOptions,
storeProfileEvent?: boolean
) => Promise<NDKUserProfile | null>
getNDKRelayList: (pubkey: Hexpubkey) => Promise<NDKRelayList>
publish: (event: NDKEvent, explicitRelayUrls?: string[]) => Promise<string[]>
}
// 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()
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' })
dexieAdapter.locking = true
const ndk = new NDK({
enableOutboxModel: true,
autoConnectUserRelays: true,
autoFetchUserMutelist: true,
explicitRelayUrls: [...DEFAULT_LOOK_UP_RELAY_LIST],
cacheAdapter: dexieAdapter
})
ndk.connect()
return ndk
}, [])
/**
* Asynchronously retrieves multiple event based on a provided filter.
*
* @param filter - The filter criteria to find the event.
* @returns Returns a promise that resolves to the found event or null if not found.
*/
const fetchEvents = async (filter: NDKFilter): Promise<NDKEvent[]> => {
return ndk
.fetchEvents(filter, {
closeOnEose: true,
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
})
.then((ndkEventSet) => {
const ndkEvents = Array.from(ndkEventSet)
return orderEventsChronologically(ndkEvents)
})
.catch((err) => {
// Log the error and show a notification if fetching fails
console.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 based on a provided filter.
*
* @param filter - The filter criteria to find the event.
* @returns Returns a promise that resolves to the found event or null if not found.
*/
const fetchEvent = async (filter: NDKFilter) => {
const events = await fetchEvents(filter)
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 | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
): Promise<NDKEvent[]> => {
// Find the user's relays (10s timeout).
const relayUrls = await Promise.race([
getRelayListForUser(hexKey, ndk),
timeout(3000)
])
.then((ndkRelayList) => {
if (ndkRelayList) return ndkRelayList[userRelaysType]
return [] // Return an empty array if ndkRelayList is undefined
})
.catch((err) => {
console.error(
`An error occurred in fetching user's (${hexKey}) ${userRelaysType}`,
err
)
return [] as string[]
})
return ndk
.fetchEvents(
filter,
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL },
relayUrls.length
? NDKRelaySet.fromRelayUrls(relayUrls, ndk, true)
: undefined
)
.then((ndkEventSet) => {
const ndkEvents = Array.from(ndkEventSet)
return orderEventsChronologically(ndkEvents)
})
.catch((err) => {
// Log the error and show a notification if fetching fails
console.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
})
}
/**
* 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 | 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,
opts?: NDKSubscriptionOptions,
storeProfileEvent?: boolean
): Promise<NDKUserProfile | null> => {
const npub = hexToNpub(pubkey)
const user = new NDKUser({ npub })
user.ndk = ndk
return await user.fetchProfile(
{
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
...(opts || {})
},
storeProfileEvent
)
}
const getNDKRelayList = async (pubkey: Hexpubkey) => {
const ndkRelayList = await Promise.race([
getRelayListForUser(pubkey, ndk),
timeout(10000)
]).catch(() => {
const relayList = new NDKRelayList(ndk)
relayList.bothRelayUrls = [SIGIT_RELAY]
return relayList
})
return ndkRelayList
}
const publish = async (
event: NDKEvent,
explicitRelayUrls?: string[]
): Promise<string[]> => {
if (!event.sig) throw new Error('Before publishing first sign the event!')
let ndkRelaySet: NDKRelaySet | undefined
if (explicitRelayUrls && explicitRelayUrls.length > 0) {
ndkRelaySet = NDKRelaySet.fromRelayUrls(explicitRelayUrls, ndk)
}
return event
.publish(ndkRelaySet, 10000)
.then((res) => {
const relaysPublishedOn = Array.from(res)
return relaysPublishedOn.map((relay) => relay.url)
})
.catch((err) => {
console.error(`An error occurred in publishing event`, err)
return []
})
}
return (
<NDKContext.Provider
value={{
ndk,
fetchEvents,
fetchEvent,
fetchEventsFromUserRelays,
fetchEventFromUserRelays,
findMetadata,
getNDKRelayList,
publish
}}
>
{children}
</NDKContext.Provider>
)
}