diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx
index 49fb142..c14372f 100644
--- a/src/components/ModCard.tsx
+++ b/src/components/ModCard.tsx
@@ -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)
})
diff --git a/src/components/ModForm.tsx b/src/components/ModForm.tsx
index 6e90b1a..763a3f9 100644
--- a/src/components/ModForm.tsx
+++ b/src/components/ModForm.tsx
@@ -13,8 +13,7 @@ import { toast } from 'react-toastify'
import { FixedSizeList as List } from 'react-window'
import { v4 as uuidv4 } from 'uuid'
import { T_TAG_VALUE } from '../constants'
-import { RelayController } from '../controllers'
-import { useAppSelector, useGames } from '../hooks'
+import { useAppSelector, useGames, useNDKContext } from '../hooks'
import { appRoutes, getModPageRoute } from '../routes'
import '../styles/styles.css'
import { DownloadUrl, ModDetails, ModFormState } from '../types'
@@ -29,6 +28,7 @@ import {
} from '../utils'
import { CheckboxField, InputError, InputField } from './Inputs'
import { LoadingSpinner } from './LoadingSpinner'
+import { NDKEvent } from '@nostr-dev-kit/ndk'
interface FormErrors {
game?: string
@@ -54,6 +54,7 @@ type ModFormProps = {
export const ModForm = ({ existingModData }: ModFormProps) => {
const location = useLocation()
const navigate = useNavigate()
+ const { ndk, publish } = useNDKContext()
const games = useGames()
const userState = useAppSelector((state) => state.user)
@@ -243,9 +244,8 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
return
}
- const publishedOnRelays = await RelayController.getInstance().publish(
- signedEvent as Event
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
// Handle cases where publishing failed or succeeded
if (publishedOnRelays.length === 0) {
@@ -763,8 +763,9 @@ const GameDropdown = ({
- Can't find the game you're looking for? You can temporarily publish the mod under '(Unlisted Game)' and
- later edit it with the proper game name once we add it.
+ Can't find the game you're looking for? You can temporarily publish the
+ mod under '(Unlisted Game)' and later edit it with the proper game name
+ once we add it.
@@ -825,10 +826,12 @@ const GameDropdown = ({
-
+
{error && }
-
Note: Please mention the game name in the body text of your mod post (e.g., 'This is a mod for Game Name')
- so we know what to look for and add.
+
+ Note: Please mention the game name in the body text of your mod post
+ (e.g., 'This is a mod for Game Name') so we know what to look for and
+ add.
)
diff --git a/src/components/ProfileSection.tsx b/src/components/ProfileSection.tsx
index e52e940..323d5a6 100644
--- a/src/components/ProfileSection.tsx
+++ b/src/components/ProfileSection.tsx
@@ -4,7 +4,6 @@ import { QRCodeSVG } from 'qrcode.react'
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { toast } from 'react-toastify'
-import { RelayController, UserRelaysType } from '../controllers'
import {
useAppSelector,
useBodyScrollDisable,
@@ -15,7 +14,7 @@ import { appRoutes, getProfilePageRoute } from '../routes'
import '../styles/author.css'
import '../styles/innerPage.css'
import '../styles/socialPosts.css'
-import { UserProfile } from '../types'
+import { UserProfile, UserRelaysType } from '../types'
import {
copyTextToClipboard,
hexToNpub,
@@ -27,6 +26,7 @@ import {
import { LoadingSpinner } from './LoadingSpinner'
import { ZapPopUp } from './Zap'
import placeholder from '../assets/img/DEGMods Placeholder Img.png'
+import { NDKEvent } from '@nostr-dev-kit/ndk'
type Props = {
pubkey: string
@@ -377,7 +377,7 @@ type FollowButtonProps = {
}
const FollowButton = ({ pubkey }: FollowButtonProps) => {
- const { fetchEventFromUserRelays } = useNDKContext()
+ const { ndk, fetchEventFromUserRelays, publish } = useNDKContext()
const [isFollowing, setIsFollowing] = useState(false)
const [isLoading, setIsLoading] = useState(false)
@@ -450,9 +450,8 @@ const FollowButton = ({ pubkey }: FollowButtonProps) => {
if (!signedEvent) return false
- const publishedOnRelays = await RelayController.getInstance().publish(
- signedEvent as Event
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
if (publishedOnRelays.length === 0) {
toast.error('Failed to publish event on any relay')
diff --git a/src/components/Zap.tsx b/src/components/Zap.tsx
index e7c7005..c393947 100644
--- a/src/components/Zap.tsx
+++ b/src/components/Zap.tsx
@@ -1,3 +1,4 @@
+import { getRelayListForUser } from '@nostr-dev-kit/ndk'
import { QRCodeSVG } from 'qrcode.react'
import React, {
Dispatch,
@@ -9,7 +10,7 @@ import React, {
} from 'react'
import Countdown, { CountdownRenderProps } from 'react-countdown'
import { toast } from 'react-toastify'
-import { MetadataController, ZapController } from '../controllers'
+import { ZapController } from '../controllers'
import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
import '../styles/popup.css'
import { PaymentRequest, UserProfile } from '../types'
@@ -251,7 +252,7 @@ export const ZapPopUp = ({
setHasZapped,
handleClose
}: ZapPopUpProps) => {
- const { findMetadata } = useNDKContext()
+ const { ndk, findMetadata } = useNDKContext()
const [isLoading, setIsLoading] = useState(false)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
const [amount, setAmount] = useState(0)
@@ -300,6 +301,20 @@ export const ZapPopUp = ({
return null
}
+ // Find the receiver's read relays.
+ const receiverRelays = await getRelayListForUser(receiver, ndk)
+ .then((ndkRelayList) => {
+ if (ndkRelayList) return ndkRelayList.readRelayUrls
+ return [] // Return an empty array if ndkRelayList is undefined
+ })
+ .catch((err) => {
+ console.error(
+ `An error occurred in getting zap receiver's read relays`,
+ err
+ )
+ return [] as string[]
+ })
+
const zapController = ZapController.getInstance()
setLoadingSpinnerDesc('Creating zap request')
@@ -308,6 +323,7 @@ export const ZapPopUp = ({
receiverMetadata.lud16,
amount,
receiverMetadata.pubkey as string,
+ receiverRelays,
userHexKey,
message,
eventId,
@@ -320,7 +336,7 @@ export const ZapPopUp = ({
.finally(() => {
setIsLoading(false)
})
- }, [amount, message, userState, receiver, eventId, aTag])
+ }, [amount, message, userState, receiver, eventId, aTag, ndk, findMetadata])
const handleGenerateQRCode = async () => {
const pr = await generatePaymentRequest()
@@ -482,7 +498,7 @@ export const ZapSplit = ({
setHasZapped,
handleClose
}: ZapSplitProps) => {
- const { findMetadata } = useNDKContext()
+ const { ndk, findMetadata } = useNDKContext()
const [isLoading, setIsLoading] = useState(false)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
const [amount, setAmount] = useState(0)
@@ -502,8 +518,8 @@ export const ZapSplit = ({
setAuthor(res)
})
- const metadataController = await MetadataController.getInstance()
- findMetadata(metadataController.adminNpubs[0]).then((res) => {
+ const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
+ findMetadata(adminNpubs[0]).then((res) => {
setAdmin(res)
})
})
@@ -557,12 +573,30 @@ export const ZapSplit = ({
const invoices = new Map()
if (authorShare > 0 && author?.pubkey && author?.lud16) {
+ // Find the receiver's read relays.
+ const authorRelays = await getRelayListForUser(
+ author.pubkey as string,
+ ndk
+ )
+ .then((ndkRelayList) => {
+ if (ndkRelayList) return ndkRelayList.readRelayUrls
+ return [] // Return an empty array if ndkRelayList is undefined
+ })
+ .catch((err) => {
+ console.error(
+ `An error occurred in getting zap receiver's read relays`,
+ err
+ )
+ return [] as string[]
+ })
+
setLoadingSpinnerDesc('Generating invoice for author')
const invoice = await zapController
.getLightningPaymentRequest(
author.lud16,
authorShare,
author.pubkey as string,
+ authorRelays,
userHexKey,
message,
eventId,
@@ -579,12 +613,27 @@ export const ZapSplit = ({
}
if (adminShare > 0 && admin?.pubkey && admin?.lud16) {
+ // Find the receiver's read relays.
+ const adminRelays = await getRelayListForUser(admin.pubkey as string, ndk)
+ .then((ndkRelayList) => {
+ if (ndkRelayList) return ndkRelayList.readRelayUrls
+ return [] // Return an empty array if ndkRelayList is undefined
+ })
+ .catch((err) => {
+ console.error(
+ `An error occurred in getting zap receiver's read relays`,
+ err
+ )
+ return [] as string[]
+ })
+
setLoadingSpinnerDesc('Generating invoice for site owner')
const invoice = await zapController
.getLightningPaymentRequest(
admin.lud16,
adminShare,
admin.pubkey as string,
+ adminRelays,
userHexKey,
message,
eventId,
diff --git a/src/contexts/NDKContext.tsx b/src/contexts/NDKContext.tsx
index 0ec3771..33e35da 100644
--- a/src/contexts/NDKContext.tsx
+++ b/src/contexts/NDKContext.tsx
@@ -3,17 +3,18 @@ import NDK, {
NDKEvent,
NDKFilter,
NDKKind,
+ NDKList,
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'
-import { UserRelaysType } from 'controllers'
import { Dexie } from 'dexie'
import { createContext, ReactNode, useEffect, useMemo } from 'react'
import { toast } from 'react-toastify'
-import { ModDetails, UserProfile } from 'types'
+import { ModDetails, MuteLists, UserProfile, UserRelaysType } from 'types'
import {
constructModListFromEvents,
hexToNpub,
@@ -33,23 +34,34 @@ type FetchModsOptions = {
interface NDKContextType {
ndk: NDK
fetchMods: (opts: FetchModsOptions) => Promise
- fetchEvents: (filter: NDKFilter, relayUrls?: string[]) => Promise
- fetchEvent: (
- filter: NDKFilter,
- relayUrls?: string[]
- ) => Promise
-
+ fetchEvents: (filter: NDKFilter) => Promise
+ fetchEvent: (filter: NDKFilter) => Promise
fetchEventsFromUserRelays: (
- filter: NDKFilter,
+ filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => Promise
fetchEventFromUserRelays: (
- filter: NDKFilter,
+ filter: NDKFilter | NDKFilter[],
hexKey: string,
userRelaysType: UserRelaysType
) => Promise
findMetadata: (pubkey: string) => Promise
+ getTotalZapAmount: (
+ user: string,
+ eTag: string,
+ aTag?: string,
+ currentLoggedInUser?: string
+ ) => Promise<{
+ accumulatedZapAmount: number
+ hasZapped: boolean
+ }>
+ publish: (event: NDKEvent) => Promise
+ getNSFWList: () => Promise
+ getMuteLists: (pubkey?: string) => Promise<{
+ admin: MuteLists
+ user: MuteLists
+ }>
}
// Create the context with an initial value of `null`
@@ -72,6 +84,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 +125,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
],
cacheAdapter: dexieAdapter
})
+ addAdminRelays(ndk)
ndk.connect()
@@ -110,33 +148,6 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
since,
limit
}: FetchModsOptions): Promise => {
- const relays = new Set()
- 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 +163,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 +189,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 => {
- const relays = new Set()
-
- // 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 => {
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 +213,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]
}
@@ -265,10 +234,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 => {
// Find the user's relays.
const relayUrls = await getRelayListForUser(hexKey, ndk)
.then((ndkRelayList) => {
@@ -285,8 +254,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
+ })
}
/**
@@ -299,7 +282,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
) => {
@@ -329,6 +312,178 @@ 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
+ }
+ }
+
+ const publish = async (event: NDKEvent): Promise => {
+ if (!event.sig) throw new Error('Before publishing first sign the event!')
+
+ return event
+ .publish(undefined, 30000)
+ .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 []
+ })
+ }
+
+ /**
+ * Retrieves a list of NSFW (Not Safe For Work) posts that were not specified as NSFW by post author but marked as NSFW by admin.
+ *
+ * @returns {Promise} - A promise that resolves to an array of NSFW post identifiers (e.g., URLs or IDs).
+ */
+ const getNSFWList = async (): Promise => {
+ // Initialize an array to store the NSFW post identifiers
+ const nsfwPosts: string[] = []
+
+ const reportingNpub = import.meta.env.VITE_REPORTING_NPUB
+
+ // Convert the public key (npub) to a hexadecimal format
+ const hexKey = npubToHex(reportingNpub)
+
+ // If the conversion is successful and we have a hexKey
+ if (hexKey) {
+ // Fetch the event that contains the NSFW list
+ const nsfwListEvent = await fetchEvent({
+ kinds: [NDKKind.ArticleCurationSet],
+ authors: [hexKey],
+ '#d': ['nsfw']
+ })
+
+ if (nsfwListEvent) {
+ // Convert the event data to an NDKList, which is a structured list format
+ const list = NDKList.from(nsfwListEvent)
+
+ // Iterate through the items in the list
+ list.items.forEach((item) => {
+ if (item[0] === 'a') {
+ // Add the identifier of the NSFW post to the nsfwPosts array
+ nsfwPosts.push(item[1])
+ }
+ })
+ }
+ }
+
+ // Return the array of NSFW post identifiers
+ return nsfwPosts
+ }
+
+ const getMuteLists = async (
+ pubkey?: string
+ ): Promise<{
+ admin: MuteLists
+ user: MuteLists
+ }> => {
+ const adminMutedAuthors = new Set()
+ const adminMutedPosts = new Set()
+
+ const reportingNpub = import.meta.env.VITE_REPORTING_NPUB
+
+ const adminHexKey = npubToHex(reportingNpub)
+
+ if (adminHexKey) {
+ const muteListEvent = await fetchEvent({
+ kinds: [NDKKind.MuteList],
+ authors: [adminHexKey]
+ })
+
+ if (muteListEvent) {
+ const list = NDKList.from(muteListEvent)
+
+ list.items.forEach((item) => {
+ if (item[0] === 'p') {
+ adminMutedAuthors.add(item[1])
+ } else if (item[0] === 'a') {
+ adminMutedPosts.add(item[1])
+ }
+ })
+ }
+ }
+
+ const userMutedAuthors = new Set()
+ const userMutedPosts = new Set()
+
+ if (pubkey) {
+ const userHexKey = npubToHex(pubkey)
+
+ if (userHexKey) {
+ const muteListEvent = await fetchEvent({
+ kinds: [NDKKind.MuteList],
+ authors: [userHexKey]
+ })
+
+ if (muteListEvent) {
+ const list = NDKList.from(muteListEvent)
+
+ list.items.forEach((item) => {
+ if (item[0] === 'p') {
+ userMutedAuthors.add(item[1])
+ } else if (item[0] === 'a') {
+ userMutedPosts.add(item[1])
+ }
+ })
+ }
+ }
+ }
+
+ return {
+ admin: {
+ authors: Array.from(adminMutedAuthors),
+ replaceableEvents: Array.from(adminMutedPosts)
+ },
+ user: {
+ authors: Array.from(userMutedAuthors),
+ replaceableEvents: Array.from(userMutedPosts)
+ }
+ }
+ }
+
return (
{
fetchEvent,
fetchEventsFromUserRelays,
fetchEventFromUserRelays,
- findMetadata
+ findMetadata,
+ getTotalZapAmount,
+ publish,
+ getNSFWList,
+ getMuteLists
}}
>
{children}
diff --git a/src/controllers/index.ts b/src/controllers/index.ts
index b7b89e4..4e84779 100644
--- a/src/controllers/index.ts
+++ b/src/controllers/index.ts
@@ -1,3 +1 @@
-export * from './metadata'
-export * from './relay'
export * from './zap'
diff --git a/src/controllers/metadata.ts b/src/controllers/metadata.ts
deleted file mode 100644
index 98967d4..0000000
--- a/src/controllers/metadata.ts
+++ /dev/null
@@ -1,217 +0,0 @@
-import NDK, { getRelayListForUser, NDKList } from '@nostr-dev-kit/ndk'
-import { kinds } from 'nostr-tools'
-import { MuteLists } from '../types'
-import { log, LogType, npubToHex, timeout } from '../utils'
-
-export enum UserRelaysType {
- Read = 'readRelayUrls',
- Write = 'writeRelayUrls',
- Both = 'bothRelayUrls'
-}
-
-/**
- * Singleton class to manage metadata operations using NDK.
- */
-export class MetadataController {
- private static instance: MetadataController
- private ndk: NDK
- public adminNpubs: string[]
- public adminRelays = new Set()
- public reportingNpub: string
-
- private constructor() {
- this.ndk = new NDK({
- explicitRelayUrls: [
- 'wss://user.kindpag.es',
- 'wss://purplepag.es',
- 'wss://relay.damus.io/',
- import.meta.env.VITE_APP_RELAY
- ]
- })
-
- this.ndk
- .connect()
- .then(() => {
- console.log('NDK connected')
- })
- .catch((err) => {
- console.log('error in ndk connection', err)
- })
-
- this.adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
- this.reportingNpub = import.meta.env.VITE_REPORTING_NPUB
- }
-
- private setAdminRelays = async () => {
- const promises = this.adminNpubs.map((npub) => {
- const hexKey = npubToHex(npub)
- if (!hexKey) return null
-
- return getRelayListForUser(hexKey, this.ndk)
- .then((ndkRelayList) => {
- if (ndkRelayList) {
- ndkRelayList.writeRelayUrls.forEach((url) =>
- this.adminRelays.add(url)
- )
- }
- })
- .catch((err) => {
- log(
- true,
- LogType.Error,
- `❌ Error occurred in getting the instance of NDKRelayList for npub: ${npub}`,
- err
- )
- })
- })
-
- await Promise.allSettled(promises)
- }
-
- /**
- * Provides the singleton instance of MetadataController.
- *
- * @returns The singleton instance of MetadataController.
- */
- public static async getInstance(): Promise {
- if (!MetadataController.instance) {
- MetadataController.instance = new MetadataController()
-
- await MetadataController.instance.setAdminRelays()
- }
- return MetadataController.instance
- }
-
- public findUserRelays = async (
- hexKey: string,
- userRelaysType: UserRelaysType = UserRelaysType.Both
- ): Promise => {
- log(true, LogType.Info, `ℹ Finding user's relays`, hexKey, userRelaysType)
-
- const ndkRelayListPromise = getRelayListForUser(hexKey, this.ndk)
-
- // Use Promise.race to either get the NDKRelayList instance or handle the timeout
- return await Promise.race([
- ndkRelayListPromise,
- timeout() // Custom timeout function that rejects after a specified time
- ])
- .then((ndkRelayList) => {
- if (ndkRelayList) return ndkRelayList[userRelaysType]
- return [] // Return an empty array if ndkRelayList is undefined
- })
- .catch((err) => {
- log(true, LogType.Error, err)
- return [] // Return an empty array if an error occurs
- })
- }
-
- public getNDKRelayList = async (hexKey: string) =>
- getRelayListForUser(hexKey, this.ndk)
-
- public getMuteLists = async (
- pubkey?: string
- ): Promise<{
- admin: MuteLists
- user: MuteLists
- }> => {
- const adminMutedAuthors = new Set()
- const adminMutedPosts = new Set()
-
- const adminHexKey = npubToHex(this.reportingNpub)
-
- if (adminHexKey) {
- const muteListEvent = await this.ndk.fetchEvent({
- kinds: [kinds.Mutelist],
- authors: [adminHexKey]
- })
-
- if (muteListEvent) {
- const list = NDKList.from(muteListEvent)
-
- list.items.forEach((item) => {
- if (item[0] === 'p') {
- adminMutedAuthors.add(item[1])
- } else if (item[0] === 'a') {
- adminMutedPosts.add(item[1])
- }
- })
- }
- }
-
- const userMutedAuthors = new Set()
- const userMutedPosts = new Set()
-
- if (pubkey) {
- const userHexKey = npubToHex(pubkey)
-
- if (userHexKey) {
- const muteListEvent = await this.ndk.fetchEvent({
- kinds: [kinds.Mutelist],
- authors: [userHexKey]
- })
-
- if (muteListEvent) {
- const list = NDKList.from(muteListEvent)
-
- list.items.forEach((item) => {
- if (item[0] === 'p') {
- userMutedAuthors.add(item[1])
- } else if (item[0] === 'a') {
- userMutedPosts.add(item[1])
- }
- })
- }
- }
- }
-
- return {
- admin: {
- authors: Array.from(adminMutedAuthors),
- replaceableEvents: Array.from(adminMutedPosts)
- },
- user: {
- authors: Array.from(userMutedAuthors),
- replaceableEvents: Array.from(userMutedPosts)
- }
- }
- }
-
- /**
- * Retrieves a list of NSFW (Not Safe For Work) posts that were not specified as NSFW by post author but marked as NSFW by admin.
- *
- * @returns {Promise} - A promise that resolves to an array of NSFW post identifiers (e.g., URLs or IDs).
- */
- public getNSFWList = async (): Promise => {
- // Initialize an array to store the NSFW post identifiers
- const nsfwPosts: string[] = []
-
- // Convert the public key (npub) to a hexadecimal format
- const hexKey = npubToHex(this.reportingNpub)
-
- // If the conversion is successful and we have a hexKey
- if (hexKey) {
- // Fetch the event that contains the NSFW list
- const nsfwListEvent = await this.ndk.fetchEvent({
- kinds: [kinds.Curationsets],
- authors: [hexKey],
- '#d': ['nsfw']
- })
-
- if (nsfwListEvent) {
- // Convert the event data to an NDKList, which is a structured list format
- const list = NDKList.from(nsfwListEvent)
-
- // Iterate through the items in the list
- list.items.forEach((item) => {
- if (item[0] === 'a') {
- // Add the identifier of the NSFW post to the nsfwPosts array
- nsfwPosts.push(item[1])
- }
- })
- }
- }
-
- // Return the array of NSFW post identifiers
- return nsfwPosts
- }
-}
diff --git a/src/controllers/relay.ts b/src/controllers/relay.ts
deleted file mode 100644
index 2fdd6ae..0000000
--- a/src/controllers/relay.ts
+++ /dev/null
@@ -1,561 +0,0 @@
-import { Event, Filter, kinds, nip57, Relay } from 'nostr-tools'
-import {
- extractZapAmount,
- log,
- LogType,
- normalizeWebSocketURL,
- timeout
-} from '../utils'
-import { MetadataController, UserRelaysType } from './metadata'
-
-/**
- * Singleton class to manage relay operations.
- */
-export class RelayController {
- private static instance: RelayController
- private events = new Map()
- private debug = true
- public connectedRelays: Relay[] = []
-
- private constructor() {}
-
- /**
- * Provides the singleton instance of RelayController.
- *
- * @returns The singleton instance of RelayController.
- */
- public static getInstance(): RelayController {
- if (!RelayController.instance) {
- RelayController.instance = new RelayController()
- }
- return RelayController.instance
- }
-
- public connectRelay = async (relayUrl: string) => {
- const relay = this.connectedRelays.find(
- (relay) =>
- normalizeWebSocketURL(relay.url) === normalizeWebSocketURL(relayUrl)
- )
- if (relay) {
- // already connected, skip
- return relay
- }
-
- return await Relay.connect(relayUrl)
- .then((relay) => {
- log(this.debug, LogType.Info, `✅ nostr (${relayUrl}): Connected!`)
- this.connectedRelays.push(relay)
- return relay
- })
- .catch((err) => {
- log(
- this.debug,
- LogType.Error,
- `❌ nostr (${relayUrl}): Connection error!`,
- err
- )
- return null
- })
- }
-
- /**
- * Publishes an event to multiple relays.
- *
- * This method establishes a connection to the application relay specified by
- * an environment variable and a set of relays obtained from the
- * `MetadataController`. It attempts to publish the event to all connected
- * relays and returns a list of URLs of relays where the event was successfully
- * published.
- *
- * If the process of finding relays or publishing the event takes too long,
- * it handles the timeout to prevent blocking the operation.
- *
- * @param event - The event to be published.
- * @param userHexKey - The user's hexadecimal public key, used to retrieve their relays.
- * If not provided, the event's public key will be used.
- * @param userRelaysType - The type of relays to be retrieved (e.g., write relays).
- * Defaults to `UserRelaysType.Write`.
- * @returns A promise that resolves to an array of URLs of relays where the event
- * was published, or an empty array if no relays were connected or the
- * event could not be published.
- */
- publish = async (
- event: Event,
- userHexKey?: string,
- userRelaysType?: UserRelaysType
- ): Promise => {
- // Connect to the application relay specified by an environment variable
- const appRelayPromise = this.connectRelay(import.meta.env.VITE_APP_RELAY)
-
- // TODO: Implement logic to retrieve relays using `window.nostr.getRelays()` once it becomes available in nostr-login.
-
- // Retrieve an instance of MetadataController to find user relays
- const metadataController = await MetadataController.getInstance()
-
- // Retrieve the list of relays for the specified user's public key
- const relayUrls = await metadataController.findUserRelays(
- userHexKey || event.pubkey,
- userRelaysType || UserRelaysType.Write
- )
-
- // Add admin relay URLs from the metadata controller to the list of relay URLs
- metadataController.adminRelays.forEach((url) => {
- relayUrls.push(url)
- })
-
- // Attempt to connect to all write relays obtained from MetadataController
- const relayPromises = relayUrls.map((relayUrl) =>
- this.connectRelay(relayUrl)
- )
-
- // Wait for all relay connection attempts to settle (either fulfilled or rejected)
- const results = await Promise.allSettled([
- appRelayPromise,
- ...relayPromises
- ])
-
- // Extract non-null values from fulfilled promises in a single pass
- const relays = results.reduce((acc, result) => {
- if (result.status === 'fulfilled') {
- const value = result.value
- if (value) {
- acc.push(value)
- }
- }
- return acc
- }, [])
-
- // If no relays are connected, log an error and return an empty array
- if (relays.length === 0) {
- log(this.debug, LogType.Error, 'No relay is connected!')
- return []
- }
-
- const publishedOnRelays: string[] = [] // Track relays where the event was successfully published
-
- // Create promises to publish the event to each connected relay
- const publishPromises = relays.map((relay) => {
- log(
- this.debug,
- LogType.Info,
- `⬆️ nostr (${relay.url}): Sending event:`,
- event
- )
-
- return Promise.race([
- relay.publish(event), // Publish the event to the relay
- timeout(30000) // Set a timeout to handle slow publishing operations
- ])
- .then((res) => {
- log(
- this.debug,
- LogType.Info,
- `⬆️ nostr (${relay.url}): Publish result:`,
- res
- )
- publishedOnRelays.push(relay.url) // Add successful relay URL to the list
- })
- .catch((err) => {
- log(
- this.debug,
- LogType.Error,
- `❌ nostr (${relay.url}): Publish error!`,
- err
- )
- })
- })
-
- // Wait for all publish operations to complete (either fulfilled or rejected)
- await Promise.allSettled(publishPromises)
-
- if (publishedOnRelays.length > 0) {
- // If the event was successfully published to any relays, check if it contains an `aTag`
- // If the `aTag` is present, cache the event locally
- const aTag = event.tags.find((item) => item[0] === 'a')
- if (aTag && aTag[1]) {
- this.events.set(aTag[1], event)
- }
- }
-
- // Return the list of relay URLs where the event was successfully published
- return publishedOnRelays
- }
-
- /**
- * Publishes an encrypted DM to receiver's read relays.
- *
- * This method connects to the application relay and a set of receiver's read relays
- * obtained from the `MetadataController`. It then publishes the event to
- * all connected relays and returns a list of relays where the event was successfully published.
- *
- * @param event - The event to be published.
- * @returns A promise that resolves to an array of URLs of relays where the event was published,
- * or an empty array if no relays were connected or the event could not be published.
- */
- publishDM = async (event: Event, receiver: string): Promise => {
- // Connect to the application relay specified by environment variable
- const appRelayPromise = this.connectRelay(import.meta.env.VITE_APP_RELAY)
-
- // todo: window.nostr.getRelays() is not implemented yet in nostr-login, implement the logic once its done
-
- const metadataController = await MetadataController.getInstance()
-
- // Retrieve the list of read relays for the receiver
- const readRelayUrls = await metadataController.findUserRelays(
- receiver,
- UserRelaysType.Read
- )
-
- // push admin relay urls obtained from metadata controller to readRelayUrls list
- metadataController.adminRelays.forEach((url) => {
- readRelayUrls.push(url)
- })
-
- // Connect to all write relays obtained from MetadataController
- const relayPromises = readRelayUrls.map((relayUrl) =>
- this.connectRelay(relayUrl)
- )
-
- // Wait for all relay connections to settle (either fulfilled or rejected)
- await Promise.allSettled([appRelayPromise, ...relayPromises])
-
- // Check if any relays are connected; if not, log an error and return null
- if (this.connectedRelays.length === 0) {
- log(this.debug, LogType.Error, 'No relay is connected!')
- return []
- }
-
- const publishedOnRelays: string[] = [] // List to track which relays successfully published the event
-
- // Create a promise for publishing the event to each connected relay
- const publishPromises = this.connectedRelays.map((relay) => {
- log(
- this.debug,
- LogType.Info,
- `⬆️ nostr (${relay.url}): Sending event:`,
- event
- )
-
- return Promise.race([
- relay.publish(event), // Publish the event to the relay
- timeout(30000) // Set a timeout to handle cases where publishing takes too long
- ])
- .then((res) => {
- log(
- this.debug,
- LogType.Info,
- `⬆️ nostr (${relay.url}): Publish result:`,
- res
- )
- publishedOnRelays.push(relay.url) // Add the relay URL to the list of successfully published relays
- })
- .catch((err) => {
- log(
- this.debug,
- LogType.Error,
- `❌ nostr (${relay.url}): Publish error!`,
- err
- )
- })
- })
-
- // Wait for all publish operations to complete (either fulfilled or rejected)
- await Promise.allSettled(publishPromises)
-
- // Return the list of relay URLs where the event was published
- return publishedOnRelays
- }
-
- /**
- * Publishes an event to multiple relays.
- *
- * This method establishes a connection to the application relay specified by
- * an environment variable and a set of relays provided as argument.
- * It attempts to publish the event to all connected relays
- * and returns a list of URLs of relays where the event was successfully published.
- *
- * If the process of publishing the event takes too long,
- * it handles the timeout to prevent blocking the operation.
- *
- * @param event - The event to be published.
- * @param relayUrls - The array of relayUrl where event should be published
- * @returns A promise that resolves to an array of URLs of relays where the event
- * was published, or an empty array if no relays were connected or the
- * event could not be published.
- */
- publishOnRelays = async (
- event: Event,
- relayUrls: string[]
- ): Promise => {
- 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((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) {
- log(this.debug, LogType.Error, 'No relay is connected!')
- return []
- }
-
- const publishedOnRelays: string[] = [] // Track relays where the event was successfully published
-
- // Create promises to publish the event to each connected relay
- const publishPromises = relays.map((relay) => {
- log(
- this.debug,
- LogType.Info,
- `⬆️ nostr (${relay.url}): Sending event:`,
- event
- )
-
- return Promise.race([
- relay.publish(event), // Publish the event to the relay
- timeout(30000) // Set a timeout to handle slow publishing operations
- ])
- .then((res) => {
- log(
- this.debug,
- LogType.Info,
- `⬆️ nostr (${relay.url}): Publish result:`,
- res
- )
- publishedOnRelays.push(relay.url) // Add successful relay URL to the list
- })
- .catch((err) => {
- log(
- this.debug,
- LogType.Error,
- `❌ nostr (${relay.url}): Publish error!`,
- err
- )
- })
- })
-
- // Wait for all publish operations to complete (either fulfilled or rejected)
- await Promise.allSettled(publishPromises)
-
- if (publishedOnRelays.length > 0) {
- // If the event was successfully published to any relays, check if it contains an `aTag`
- // If the `aTag` is present, cache the event locally
- const aTag = event.tags.find((item) => item[0] === 'a')
- if (aTag && aTag[1]) {
- this.events.set(aTag[1], event)
- }
- }
-
- // Return the list of relay URLs where the event was successfully published
- 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((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,
- 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((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() // 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((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
- }
- }
-}
diff --git a/src/controllers/zap.ts b/src/controllers/zap.ts
index 8b74dd7..0ff300a 100644
--- a/src/controllers/zap.ts
+++ b/src/controllers/zap.ts
@@ -17,7 +17,6 @@ import {
ZapRequest
} from '../types'
import { log, LogType, npubToHex } from '../utils'
-import { MetadataController, UserRelaysType } from './metadata'
/**
* Singleton class to manage zap related operations.
@@ -48,6 +47,7 @@ export class ZapController {
* @param lud16 - LUD-16 of the recipient.
* @param amount - payment amount (will be multiplied by 1000 to represent sats).
* @param recipientPubKey - pubKey of the recipient.
+ * @param recipientRelays - relays on which zap receipt will be published.
* @param senderPubkey - pubKey of of the sender.
* @param content - optional content (comment).
* @param eventId - event id, if zapping an event.
@@ -59,6 +59,7 @@ export class ZapController {
lud16: string,
amount: number,
recipientPubKey: string,
+ recipientRelays: string[],
senderPubkey: string,
content?: string,
eventId?: string,
@@ -88,6 +89,7 @@ export class ZapController {
amount,
content,
recipientPubKey,
+ recipientRelays,
senderPubkey,
eventId,
aTag
@@ -273,6 +275,7 @@ export class ZapController {
* @param amount - request amount (sats).
* @param content - comment.
* @param recipientPubKey - pubKey of the recipient.
+ * @param recipientRelays - relays on which zap receipt will be published.
* @param senderPubkey - pubKey of of the sender.
* @param eventId - event id, if zapping an event.
* @param aTag - value of `a` tag.
@@ -282,6 +285,7 @@ export class ZapController {
amount: number,
content = '',
recipientPubKey: string,
+ recipientRelays: string[],
senderPubkey: string,
eventId?: string,
aTag?: string
@@ -290,21 +294,15 @@ export class ZapController {
if (!recipientHexKey) throw 'Invalid recipient pubKey.'
- const metadataController = await MetadataController.getInstance()
- const receiverReadRelays = await metadataController.findUserRelays(
- recipientHexKey,
- UserRelaysType.Read
- )
-
- if (!receiverReadRelays.includes(this.appRelay)) {
- receiverReadRelays.push(this.appRelay)
+ if (!recipientRelays.includes(this.appRelay)) {
+ recipientRelays.push(this.appRelay)
}
const zapRequest: ZapRequest = {
kind: kinds.ZapRequest,
content,
tags: [
- ['relays', ...receiverReadRelays],
+ ['relays', ...recipientRelays],
['amount', `${amount}`],
['p', recipientHexKey]
],
diff --git a/src/hooks/useComments.ts b/src/hooks/useComments.ts
index 5dd120a..6be5932 100644
--- a/src/hooks/useComments.ts
+++ b/src/hooks/useComments.ts
@@ -6,9 +6,8 @@ import {
NDKSubscription,
NDKSubscriptionCacheUsage
} from '@nostr-dev-kit/ndk'
-import { UserRelaysType } from 'controllers'
import { useEffect, useState } from 'react'
-import { CommentEvent, ModDetails } from 'types'
+import { CommentEvent, ModDetails, UserRelaysType } from 'types'
import { log, LogType } from 'utils'
import { useNDKContext } from './useNDKContext'
diff --git a/src/hooks/useMuteLists.ts b/src/hooks/useMuteLists.ts
index 558bcb7..803da9e 100644
--- a/src/hooks/useMuteLists.ts
+++ b/src/hooks/useMuteLists.ts
@@ -1,9 +1,10 @@
import { useEffect, useState } from 'react'
import { MuteLists } from 'types'
import { useAppSelector } from './redux'
-import { MetadataController } from 'controllers'
+import { useNDKContext } from './useNDKContext'
export const useMuteLists = () => {
+ const { getMuteLists } = useNDKContext()
const [muteLists, setMuteLists] = useState<{
admin: MuteLists
user: MuteLists
@@ -21,17 +22,11 @@ export const useMuteLists = () => {
const userState = useAppSelector((state) => state.user)
useEffect(() => {
- const getMuteLists = async () => {
- const pubkey = userState.user?.pubkey as string | undefined
-
- const metadataController = await MetadataController.getInstance()
- metadataController.getMuteLists(pubkey).then((lists) => {
- setMuteLists(lists)
- })
- }
-
- getMuteLists()
- }, [userState])
+ const pubkey = userState.user?.pubkey as string | undefined
+ getMuteLists(pubkey).then((lists) => {
+ setMuteLists(lists)
+ })
+ }, [userState, getMuteLists])
return muteLists
}
diff --git a/src/hooks/useNDKContext.ts b/src/hooks/useNDKContext.ts
index b551d5e..f7383df 100644
--- a/src/hooks/useNDKContext.ts
+++ b/src/hooks/useNDKContext.ts
@@ -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 }
}
diff --git a/src/hooks/useNSFWList.ts b/src/hooks/useNSFWList.ts
index 0712da6..9da98db 100644
--- a/src/hooks/useNSFWList.ts
+++ b/src/hooks/useNSFWList.ts
@@ -1,14 +1,13 @@
-import { MetadataController } from 'controllers'
import { useState } from 'react'
import { useDidMount } from './useDidMount'
+import { useNDKContext } from './useNDKContext'
export const useNSFWList = () => {
+ const { getNSFWList } = useNDKContext()
const [nsfwList, setNSFWList] = useState([])
useDidMount(async () => {
- const metadataController = await MetadataController.getInstance()
-
- metadataController.getNSFWList().then((list) => {
+ getNSFWList().then((list) => {
setNSFWList(list)
})
})
diff --git a/src/hooks/useReactions.ts b/src/hooks/useReactions.ts
index 9ebc63f..574c3eb 100644
--- a/src/hooks/useReactions.ts
+++ b/src/hooks/useReactions.ts
@@ -1,10 +1,10 @@
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk'
import { REACTIONS } from 'constants.ts'
-import { RelayController, UserRelaysType } from 'controllers'
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
import { useMemo, useState } from 'react'
import { toast } from 'react-toastify'
+import { UserRelaysType } from 'types'
import { abbreviateNumber, log, LogType, now } from 'utils'
type UseReactionsParams = {
@@ -14,7 +14,7 @@ type UseReactionsParams = {
}
export const useReactions = (params: UseReactionsParams) => {
- const { ndk, fetchEventsFromUserRelays } = useNDKContext()
+ const { ndk, fetchEventsFromUserRelays, publish } = useNDKContext()
const [isReactionInProgress, setIsReactionInProgress] = useState(false)
const [isDataLoaded, setIsDataLoaded] = useState(false)
const [reactionEvents, setReactionEvents] = useState([])
@@ -119,13 +119,11 @@ export const useReactions = (params: UseReactionsParams) => {
if (!signedEvent) return
- setReactionEvents((prev) => [...prev, new NDKEvent(ndk, signedEvent)])
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
- const publishedOnRelays = await RelayController.getInstance().publish(
- signedEvent as Event,
- params.pubkey,
- UserRelaysType.Read
- )
+ setReactionEvents((prev) => [...prev, ndkEvent])
+
+ const publishedOnRelays = await publish(ndkEvent)
if (publishedOnRelays.length === 0) {
log(
diff --git a/src/layout/header.tsx b/src/layout/header.tsx
index faae13e..011b7cf 100644
--- a/src/layout/header.tsx
+++ b/src/layout/header.tsx
@@ -6,7 +6,6 @@ import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { Banner } from '../components/Banner'
import { ZapPopUp } from '../components/Zap'
-import { MetadataController } from '../controllers'
import {
useAppDispatch,
useAppSelector,
@@ -277,8 +276,8 @@ const TipButtonWithDialog = React.memo(() => {
useBodyScrollDisable(isOpen)
useDidMount(async () => {
- const metadataController = await MetadataController.getInstance()
- setAdminNpub(metadataController.adminNpubs[0])
+ const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
+ setAdminNpub(adminNpubs[0])
})
return (
diff --git a/src/pages/home.tsx b/src/pages/home.tsx
index 79e5a15..5c15e25 100644
--- a/src/pages/home.tsx
+++ b/src/pages/home.tsx
@@ -152,7 +152,7 @@ const SlideContent = ({ naddr }: SlideContentProps) => {
useDidMount(() => {
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
- const { identifier, kind, pubkey, relays = [] } = decoded.data
+ const { identifier, kind, pubkey } = decoded.data
const ndkFilter: NDKFilter = {
'#a': [identifier],
@@ -160,7 +160,7 @@ const SlideContent = ({ naddr }: SlideContentProps) => {
kinds: [kind]
}
- fetchEvent(ndkFilter, relays)
+ fetchEvent(ndkFilter)
.then((ndkEvent) => {
if (ndkEvent) {
const extracted = extractModData(ndkEvent)
@@ -225,7 +225,7 @@ const DisplayMod = ({ naddr }: DisplayModProps) => {
useDidMount(() => {
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
- const { identifier, kind, pubkey, relays = [] } = decoded.data
+ const { identifier, kind, pubkey } = decoded.data
const ndkFilter: NDKFilter = {
'#a': [identifier],
@@ -233,7 +233,7 @@ const DisplayMod = ({ naddr }: DisplayModProps) => {
kinds: [kind]
}
- fetchEvent(ndkFilter, relays)
+ fetchEvent(ndkFilter)
.then((ndkEvent) => {
if (ndkEvent) {
const extracted = extractModData(ndkEvent)
diff --git a/src/pages/mod/index.tsx b/src/pages/mod/index.tsx
index ccf172b..8a39cc4 100644
--- a/src/pages/mod/index.tsx
+++ b/src/pages/mod/index.tsx
@@ -11,7 +11,6 @@ import { toast } from 'react-toastify'
import { BlogCard } from '../../components/BlogCard'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { ProfileSection } from '../../components/ProfileSection'
-import { MetadataController, UserRelaysType } from '../../controllers'
import {
useAppSelector,
useBodyScrollDisable,
@@ -29,7 +28,7 @@ import '../../styles/styles.css'
import '../../styles/tabs.css'
import '../../styles/tags.css'
import '../../styles/write.css'
-import { DownloadUrl, ModDetails } from '../../types'
+import { DownloadUrl, ModDetails, UserRelaysType } from '../../types'
import {
abbreviateNumber,
copyTextToClipboard,
@@ -58,7 +57,7 @@ export const ModPage = () => {
useDidMount(async () => {
if (naddr) {
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
- const { identifier, kind, pubkey, relays = [] } = decoded.data
+ const { identifier, kind, pubkey } = decoded.data
const filter: NDKFilter = {
'#a': [identifier],
@@ -66,7 +65,7 @@ export const ModPage = () => {
kinds: [kind]
}
- fetchEvent(filter, relays)
+ fetchEvent(filter)
.then((event) => {
if (event) {
const extracted = extractModData(event)
@@ -217,7 +216,7 @@ type GameProps = {
}
const Game = ({ naddr, game, author, aTag }: GameProps) => {
- const { fetchEventFromUserRelays } = useNDKContext()
+ const { ndk, fetchEventFromUserRelays, publish } = useNDKContext()
const userState = useAppSelector((state) => state.user)
const [isLoading, setIsLoading] = useState(false)
@@ -350,7 +349,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
setLoadingSpinnerDesc('Updating mute list event')
- const isUpdated = await signAndPublish(unsignedEvent)
+ const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
if (isUpdated) {
setIsBlocked(true)
}
@@ -391,7 +390,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
}
setLoadingSpinnerDesc('Updating mute list event')
- const isUpdated = await signAndPublish(unsignedEvent)
+ const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
if (isUpdated) {
setIsBlocked(false)
}
@@ -457,7 +456,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
setLoadingSpinnerDesc('Updating nsfw list event')
- const isUpdated = await signAndPublish(unsignedEvent)
+ const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
if (isUpdated) {
setIsAddedToNSFW(true)
}
@@ -498,7 +497,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
}
setLoadingSpinnerDesc('Updating nsfw list event')
- const isUpdated = await signAndPublish(unsignedEvent)
+ const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
if (isUpdated) {
setIsAddedToNSFW(false)
}
@@ -668,7 +667,7 @@ type ReportPopupProps = {
}
const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
- const { fetchEventFromUserRelays } = useNDKContext()
+ const { ndk, fetchEventFromUserRelays, publish } = useNDKContext()
const userState = useAppSelector((state) => state.user)
const [selectedOptions, setSelectedOptions] = useState({
actuallyCP: false,
@@ -715,8 +714,8 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
return
}
- const metadataController = await MetadataController.getInstance()
- const reportingPubkey = npubToHex(metadataController.reportingNpub)
+ const reportingNpub = import.meta.env.VITE_REPORTING_NPUB
+ const reportingPubkey = npubToHex(reportingNpub)
if (reportingPubkey === hexPubkey) {
setLoadingSpinnerDesc(`Finding user's mute list`)
@@ -767,7 +766,7 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
}
setLoadingSpinnerDesc('Updating mute list event')
- const isUpdated = await signAndPublish(unsignedEvent)
+ const isUpdated = await signAndPublish(unsignedEvent, ndk, publish)
if (isUpdated) handleClose()
} else {
const href = window.location.href
@@ -780,7 +779,12 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
})
setLoadingSpinnerDesc('Sending report')
- const isSent = await sendDMUsingRandomKey(message, reportingPubkey!)
+ const isSent = await sendDMUsingRandomKey(
+ message,
+ reportingPubkey!,
+ ndk,
+ publish
+ )
if (isSent) handleClose()
}
setIsLoading(false)
diff --git a/src/pages/mod/internal/comment/index.tsx b/src/pages/mod/internal/comment/index.tsx
index 3c92b25..f238524 100644
--- a/src/pages/mod/internal/comment/index.tsx
+++ b/src/pages/mod/internal/comment/index.tsx
@@ -1,9 +1,5 @@
+import { NDKEvent } from '@nostr-dev-kit/ndk'
import { ZapPopUp } from 'components/Zap'
-import {
- MetadataController,
- RelayController,
- UserRelaysType
-} from 'controllers'
import { formatDate } from 'date-fns'
import {
useAppSelector,
@@ -53,6 +49,7 @@ type Props = {
}
export const Comments = ({ modDetails, setCommentCount }: Props) => {
+ const { ndk, publish } = useNDKContext()
const { commentEvents, setCommentEvents } = useComments(modDetails)
const [filterOptions, setFilterOptions] = useState({
sort: SortByEnum.Latest,
@@ -88,7 +85,8 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
created_at: now(),
tags: [
['e', modDetails.id],
- ['a', modDetails.aTag]
+ ['a', modDetails.aTag],
+ ['p', modDetails.author]
]
}
@@ -111,28 +109,52 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
...prev
])
- const publish = async () => {
- const metadataController = await MetadataController.getInstance()
- const modAuthorReadRelays = await metadataController.findUserRelays(
- modDetails.author,
- UserRelaysType.Read
- )
- const commentatorWriteRelays = await metadataController.findUserRelays(
- pubkey,
- UserRelaysType.Write
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ publish(ndkEvent)
+ .then((publishedOnRelays) => {
+ if (publishedOnRelays.length === 0) {
+ setCommentEvents((prev) =>
+ prev.map((event) => {
+ if (event.id === signedEvent.id) {
+ return {
+ ...event,
+ status: CommentEventStatus.Failed
+ }
+ }
- const combinedRelays = [
- ...new Set(...modAuthorReadRelays, ...commentatorWriteRelays)
- ]
+ return event
+ })
+ )
+ } else {
+ setCommentEvents((prev) =>
+ prev.map((event) => {
+ if (event.id === signedEvent.id) {
+ return {
+ ...event,
+ status: CommentEventStatus.Published
+ }
+ }
- const publishedOnRelays =
- await RelayController.getInstance().publishOnRelays(
- signedEvent,
- combinedRelays
- )
+ return event
+ })
+ )
+ }
- if (publishedOnRelays.length === 0) {
+ // when an event is successfully published remove the status from it after 15 seconds
+ setTimeout(() => {
+ setCommentEvents((prev) =>
+ prev.map((event) => {
+ if (event.id === signedEvent.id) {
+ delete event.status
+ }
+
+ return event
+ })
+ )
+ }, 15000)
+ })
+ .catch((err) => {
+ console.error('An error occurred in publishing comment', err)
setCommentEvents((prev) =>
prev.map((event) => {
if (event.id === signedEvent.id) {
@@ -145,36 +167,7 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
return event
})
)
- } else {
- setCommentEvents((prev) =>
- prev.map((event) => {
- if (event.id === signedEvent.id) {
- return {
- ...event,
- status: CommentEventStatus.Published
- }
- }
-
- return event
- })
- )
- }
-
- // when an event is successfully published remove the status from it after 15 seconds
- setTimeout(() => {
- setCommentEvents((prev) =>
- prev.map((event) => {
- if (event.id === signedEvent.id) {
- delete event.status
- }
-
- return event
- })
- )
- }, 15000)
- }
-
- publish()
+ })
return true
}
@@ -502,22 +495,21 @@ 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()
useBodyScrollDisable(isOpen)
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)
diff --git a/src/pages/mod/internal/zap/index.tsx b/src/pages/mod/internal/zap/index.tsx
index e40a07c..996c8d2 100644
--- a/src/pages/mod/internal/zap/index.tsx
+++ b/src/pages/mod/internal/zap/index.tsx
@@ -1,6 +1,10 @@
import { ZapSplit } from 'components/Zap'
-import { RelayController } from 'controllers'
-import { useAppSelector, useBodyScrollDisable, useDidMount } from 'hooks'
+import {
+ useAppSelector,
+ useBodyScrollDisable,
+ useDidMount,
+ useNDKContext
+} from 'hooks'
import { useState } from 'react'
import { toast } from 'react-toastify'
import { ModDetails } from 'types'
@@ -12,22 +16,21 @@ 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()
useBodyScrollDisable(isOpen)
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)
diff --git a/src/pages/search.tsx b/src/pages/search.tsx
index 41aebcb..f9f3cac 100644
--- a/src/pages/search.tsx
+++ b/src/pages/search.tsx
@@ -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)
diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx
index 60aa2c8..4c0f618 100644
--- a/src/pages/settings/index.tsx
+++ b/src/pages/settings/index.tsx
@@ -1,5 +1,4 @@
import { AdminSVG, PreferenceSVG, ProfileSVG, RelaySVG } from 'components/SVGs'
-import { MetadataController } from 'controllers'
import { useAppSelector } from 'hooks'
import { logout } from 'nostr-login'
import { useEffect, useState } from 'react'
@@ -57,15 +56,12 @@ const SettingTabs = () => {
const userState = useAppSelector((state) => state.user)
useEffect(() => {
- MetadataController.getInstance().then((controller) => {
- if (userState.auth && userState.user?.npub) {
- setIsAdmin(
- controller.adminNpubs.includes(userState.user.npub as string)
- )
- } else {
- setIsAdmin(false)
- }
- })
+ const adminNpubs = import.meta.env.VITE_ADMIN_NPUBS.split(',')
+ if (userState.auth && userState.user?.npub) {
+ setIsAdmin(adminNpubs.includes(userState.user.npub as string))
+ } else {
+ setIsAdmin(false)
+ }
}, [userState])
const handleSignOut = () => {
diff --git a/src/pages/settings/profile.tsx b/src/pages/settings/profile.tsx
index 52f69f7..907d0f0 100644
--- a/src/pages/settings/profile.tsx
+++ b/src/pages/settings/profile.tsx
@@ -1,6 +1,6 @@
import { InputField } from 'components/Inputs'
import { ProfileQRButtonWithPopUp } from 'components/ProfileSection'
-import { useAppDispatch, useAppSelector } from 'hooks'
+import { useAppDispatch, useAppSelector, useNDKContext } from 'hooks'
import { kinds, nip19, UnsignedEvent, Event } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
@@ -14,7 +14,6 @@ import {
profileFromEvent,
serializeProfile
} from '@nostr-dev-kit/ndk'
-import { RelayController } from 'controllers'
import { LoadingSpinner } from 'components/LoadingSpinner'
import { setUser } from 'store/reducers/user'
import placeholderMod from '../../assets/img/DEGMods Placeholder Img.png'
@@ -43,6 +42,7 @@ const defaultFormState: FormState = {
export const ProfileSettings = () => {
const dispatch = useAppDispatch()
const userState = useAppSelector((state) => state.user)
+ const { ndk, publish } = useNDKContext()
const [isPublishing, setIsPublishing] = useState(false)
const [formState, setFormState] = useState(defaultFormState)
@@ -163,9 +163,8 @@ export const ProfileSettings = () => {
return
}
- const publishedOnRelays = await RelayController.getInstance().publish(
- signedEvent as Event
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
// Handle cases where publishing failed or succeeded
if (publishedOnRelays.length === 0) {
diff --git a/src/pages/settings/relay.tsx b/src/pages/settings/relay.tsx
index ee79d94..1a32d28 100644
--- a/src/pages/settings/relay.tsx
+++ b/src/pages/settings/relay.tsx
@@ -1,21 +1,23 @@
-import { NDKRelayList } from '@nostr-dev-kit/ndk'
+import {
+ getRelayListForUser,
+ NDKEvent,
+ NDKRelayList,
+ NDKRelayStatus
+} from '@nostr-dev-kit/ndk'
import { InputField } from 'components/Inputs'
import { LoadingSpinner } from 'components/LoadingSpinner'
-import {
- MetadataController,
- RelayController,
- UserRelaysType
-} from 'controllers'
-import { useAppSelector, useDidMount } from 'hooks'
+import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { toast } from 'react-toastify'
+import { UserRelaysType } from 'types'
import { log, LogType, normalizeWebSocketURL, now } from 'utils'
const READ_MARKER = 'read'
const WRITE_MARKER = 'write'
export const RelaySettings = () => {
+ const { ndk, publish } = useNDKContext()
const userState = useAppSelector((state) => state.user)
const [ndkRelayList, setNDKRelayList] = useState(null)
const [isPublishing, setIsPublishing] = useState(false)
@@ -23,10 +25,8 @@ export const RelaySettings = () => {
const [inputValue, setInputValue] = useState('')
useEffect(() => {
- const fetchRelayList = async (pubkey: string) => {
- const metadataController = await MetadataController.getInstance()
- metadataController
- .getNDKRelayList(pubkey)
+ if (userState.auth && userState.user?.pubkey) {
+ getRelayListForUser(userState.user.pubkey as string, ndk)
.then((res) => {
setNDKRelayList(res)
})
@@ -38,14 +38,10 @@ export const RelaySettings = () => {
)
setNDKRelayList(null)
})
- }
-
- if (userState.auth && userState.user?.pubkey) {
- fetchRelayList(userState.user.pubkey as string)
} else {
setNDKRelayList(null)
}
- }, [userState])
+ }, [userState, ndk])
const handleAdd = async (relayUrl: string) => {
if (!ndkRelayList) return
@@ -78,11 +74,8 @@ export const RelaySettings = () => {
return
}
- const publishedOnRelays =
- await RelayController.getInstance().publishOnRelays(
- signedEvent,
- ndkRelayList.writeRelayUrls
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
// Handle cases where publishing failed or succeeded
if (publishedOnRelays.length === 0) {
@@ -140,11 +133,8 @@ export const RelaySettings = () => {
return
}
- const publishedOnRelays =
- await RelayController.getInstance().publishOnRelays(
- signedEvent,
- ndkRelayList.writeRelayUrls
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
// Handle cases where publishing failed or succeeded
if (publishedOnRelays.length === 0) {
@@ -214,11 +204,8 @@ export const RelaySettings = () => {
return
}
- const publishedOnRelays =
- await RelayController.getInstance().publishOnRelays(
- signedEvent,
- ndkRelayList.writeRelayUrls
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
// Handle cases where publishing failed or succeeded
if (publishedOnRelays.length === 0) {
@@ -382,17 +369,29 @@ const RelayListItem = ({
changeRelayType
}: RelayItemProps) => {
const [isConnected, setIsConnected] = useState(false)
+ const { ndk } = useNDKContext()
useDidMount(() => {
- RelayController.getInstance()
- .connectRelay(relayUrl)
- .then((relay) => {
- if (relay && relay.connected) {
- setIsConnected(true)
- } else {
- setIsConnected(false)
- }
- })
+ const ndkPool = ndk.pool
+
+ ndkPool.on('relay:connect', (relay) => {
+ if (relay.url === relayUrl) {
+ setIsConnected(true)
+ }
+ })
+
+ ndkPool.on('relay:disconnect', (relay) => {
+ if (relay.url === relayUrl) {
+ setIsConnected(false)
+ }
+ })
+
+ const relay = ndkPool.relays.get(relayUrl)
+ if (relay && relay.status >= NDKRelayStatus.CONNECTED) {
+ setIsConnected(true)
+ } else {
+ setIsConnected(false)
+ }
})
return (
diff --git a/src/pages/submitMod.tsx b/src/pages/submitMod.tsx
index 226b896..ba79874 100644
--- a/src/pages/submitMod.tsx
+++ b/src/pages/submitMod.tsx
@@ -29,7 +29,7 @@ export const SubmitModPage = () => {
useDidMount(async () => {
if (naddr) {
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
- const { identifier, kind, pubkey, relays = [] } = decoded.data
+ const { identifier, kind, pubkey } = decoded.data
const filter: NDKFilter = {
'#a': [identifier],
@@ -39,7 +39,7 @@ export const SubmitModPage = () => {
setIsFetching(true)
- fetchEvent(filter, relays)
+ fetchEvent(filter)
.then((event) => {
if (event) {
const extracted = extractModData(event)
diff --git a/src/types/user.ts b/src/types/user.ts
index 551bd1f..059ba84 100644
--- a/src/types/user.ts
+++ b/src/types/user.ts
@@ -1,3 +1,9 @@
import { NDKUserProfile } from '@nostr-dev-kit/ndk'
export type UserProfile = NDKUserProfile | null
+
+export enum UserRelaysType {
+ Read = 'readRelayUrls',
+ Write = 'writeRelayUrls',
+ Both = 'bothRelayUrls'
+}
diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts
index f3711b0..130d023 100644
--- a/src/utils/nostr.ts
+++ b/src/utils/nostr.ts
@@ -9,9 +9,8 @@ import {
UnsignedEvent
} from 'nostr-tools'
import { toast } from 'react-toastify'
-import { RelayController } from '../controllers'
import { log, LogType } from './utils'
-import { NDKEvent } from '@nostr-dev-kit/ndk'
+import NDK, { NDKEvent } from '@nostr-dev-kit/ndk'
/**
* Get the current time in seconds since the Unix epoch (January 1, 1970).
@@ -123,7 +122,11 @@ export const extractZapAmount = (event: Event): number => {
* @param unsignedEvent - The event object which needs to be signed before publishing.
* @returns - A promise that resolves to boolean indicating whether the event was successfully signed and published
*/
-export const signAndPublish = async (unsignedEvent: UnsignedEvent) => {
+export const signAndPublish = async (
+ unsignedEvent: UnsignedEvent,
+ ndk: NDK,
+ publish: (event: NDKEvent) => Promise
+) => {
// Sign the event. This returns a signed event or null if signing fails.
const signedEvent = await window.nostr
?.signEvent(unsignedEvent)
@@ -138,11 +141,10 @@ export const signAndPublish = async (unsignedEvent: UnsignedEvent) => {
// If the event couldn't be signed, exit the function and return null.
if (!signedEvent) return false
- // Publish the signed event to the relays using the RelayController.
+ // Publish the signed event to the relays.
// This returns an array of relay URLs where the event was successfully published.
- const publishedOnRelays = await RelayController.getInstance().publish(
- signedEvent as Event
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
// Handle cases where publishing to the relays failed
if (publishedOnRelays.length === 0) {
@@ -170,7 +172,9 @@ export const signAndPublish = async (unsignedEvent: UnsignedEvent) => {
*/
export const sendDMUsingRandomKey = async (
message: string,
- receiver: string
+ receiver: string,
+ ndk: NDK,
+ publish: (event: NDKEvent) => Promise
) => {
// Generate a random secret key for encrypting the message
const secretKey = generateSecretKey()
@@ -201,11 +205,8 @@ export const sendDMUsingRandomKey = async (
// Finalize and sign the event using the generated secret key
const signedEvent = finalizeEvent(unsignedEvent, secretKey)
- // Publish the signed event (the encrypted DM) to the relays
- const publishedOnRelays = await RelayController.getInstance().publishDM(
- signedEvent,
- receiver
- )
+ const ndkEvent = new NDKEvent(ndk, signedEvent)
+ const publishedOnRelays = await publish(ndkEvent)
// Handle cases where publishing to the relays failed
if (publishedOnRelays.length === 0) {