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 170c1e4..972c8ef 100644 --- a/src/components/ProfileSection.tsx +++ b/src/components/ProfileSection.tsx @@ -4,7 +4,7 @@ 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 { UserRelaysType } from '../controllers' import { useAppSelector, useDidMount, useNDKContext } from '../hooks' import { appRoutes, getProfilePageRoute } from '../routes' import '../styles/author.css' @@ -22,6 +22,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 @@ -368,7 +369,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) @@ -441,9 +442,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/contexts/NDKContext.tsx b/src/contexts/NDKContext.tsx index da9c2c4..0e92db9 100644 --- a/src/contexts/NDKContext.tsx +++ b/src/contexts/NDKContext.tsx @@ -56,6 +56,7 @@ interface NDKContextType { accumulatedZapAmount: number hasZapped: boolean }> + publish: (event: NDKEvent) => Promise } // Create the context with an initial value of `null` @@ -352,6 +353,21 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => { } } + 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 [] + }) + } + return ( { fetchEventsFromUserRelays, fetchEventFromUserRelays, findMetadata, - getTotalZapAmount + getTotalZapAmount, + publish }} > {children} diff --git a/src/controllers/index.ts b/src/controllers/index.ts index b7b89e4..b028b93 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -1,3 +1,2 @@ export * from './metadata' -export * from './relay' export * from './zap' diff --git a/src/controllers/relay.ts b/src/controllers/relay.ts deleted file mode 100644 index e8855c6..0000000 --- a/src/controllers/relay.ts +++ /dev/null @@ -1,368 +0,0 @@ -import { Event, Relay } from 'nostr-tools' -import { 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 - } -} diff --git a/src/hooks/useReactions.ts b/src/hooks/useReactions.ts index 9ebc63f..60406c2 100644 --- a/src/hooks/useReactions.ts +++ b/src/hooks/useReactions.ts @@ -1,6 +1,6 @@ import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk' import { REACTIONS } from 'constants.ts' -import { RelayController, UserRelaysType } from 'controllers' +import { UserRelaysType } from 'controllers' import { useAppSelector, useDidMount, useNDKContext } from 'hooks' import { Event, kinds, UnsignedEvent } from 'nostr-tools' import { useMemo, useState } from 'react' @@ -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/pages/mod/index.tsx b/src/pages/mod/index.tsx index a85d716..dd678fd 100644 --- a/src/pages/mod/index.tsx +++ b/src/pages/mod/index.tsx @@ -53,7 +53,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], @@ -61,7 +61,7 @@ export const ModPage = () => { kinds: [kind] } - fetchEvent(filter, relays) + fetchEvent(filter) .then((event) => { if (event) { const extracted = extractModData(event) @@ -212,7 +212,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) @@ -343,7 +343,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) } @@ -384,7 +384,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) } @@ -450,7 +450,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) } @@ -491,7 +491,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) } @@ -661,7 +661,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, @@ -760,7 +760,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 @@ -773,7 +773,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 aeeaf7b..a82c87d 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, useDidMount, useNDKContext, useReactions } from 'hooks' import { useComments } from 'hooks/useComments' @@ -47,6 +43,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, @@ -82,7 +79,8 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => { created_at: now(), tags: [ ['e', modDetails.id], - ['a', modDetails.aTag] + ['a', modDetails.aTag], + ['p', modDetails.author] ] } @@ -105,28 +103,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) { @@ -139,36 +161,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 } 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..90beed2 100644 --- a/src/pages/settings/relay.tsx +++ b/src/pages/settings/relay.tsx @@ -1,12 +1,8 @@ -import { NDKRelayList } from '@nostr-dev-kit/ndk' +import { 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 { MetadataController, UserRelaysType } from 'controllers' +import { useAppSelector, useDidMount, useNDKContext } from 'hooks' import { Event, kinds, UnsignedEvent } from 'nostr-tools' import { useEffect, useState } from 'react' import { toast } from 'react-toastify' @@ -16,6 +12,7 @@ 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) @@ -78,11 +75,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 +134,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 +205,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 +370,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/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) {