feat: refactor code to use ndk
All checks were successful
Release to Staging / build_and_release (push) Successful in 45s
All checks were successful
Release to Staging / build_and_release (push) Successful in 45s
This commit is contained in:
parent
44acba8d26
commit
8529a95718
@ -4,12 +4,8 @@ import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import {
|
||||
MetadataController,
|
||||
RelayController,
|
||||
UserRelaysType
|
||||
} from '../controllers'
|
||||
import { useAppSelector, useDidMount } from '../hooks'
|
||||
import { RelayController, UserRelaysType } from '../controllers'
|
||||
import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
|
||||
import { appRoutes, getProfilePageRoute } from '../routes'
|
||||
import '../styles/author.css'
|
||||
import '../styles/innerPage.css'
|
||||
@ -31,11 +27,11 @@ type Props = {
|
||||
}
|
||||
|
||||
export const ProfileSection = ({ pubkey }: Props) => {
|
||||
const { findMetadata } = useNDKContext()
|
||||
const [profile, setProfile] = useState<UserProfile>()
|
||||
|
||||
useDidMount(async () => {
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
metadataController.findMetadata(pubkey).then((res) => {
|
||||
useDidMount(() => {
|
||||
findMetadata(pubkey).then((res) => {
|
||||
setProfile(res)
|
||||
})
|
||||
})
|
||||
@ -371,6 +367,7 @@ type FollowButtonProps = {
|
||||
}
|
||||
|
||||
const FollowButton = ({ pubkey }: FollowButtonProps) => {
|
||||
const { fetchEventFromUserRelays } = useNDKContext()
|
||||
const [isFollowing, setIsFollowing] = useState(false)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@ -409,12 +406,11 @@ const FollowButton = ({ pubkey }: FollowButtonProps) => {
|
||||
authors: [userHexKey]
|
||||
}
|
||||
|
||||
const contactListEvent =
|
||||
await RelayController.getInstance().fetchEventFromUserRelays(
|
||||
filter,
|
||||
userHexKey,
|
||||
UserRelaysType.Both
|
||||
)
|
||||
const contactListEvent = await fetchEventFromUserRelays(
|
||||
filter,
|
||||
userHexKey,
|
||||
UserRelaysType.Both
|
||||
)
|
||||
|
||||
if (!contactListEvent)
|
||||
return {
|
||||
@ -513,12 +509,11 @@ const FollowButton = ({ pubkey }: FollowButtonProps) => {
|
||||
authors: [userHexKey]
|
||||
}
|
||||
|
||||
const contactListEvent =
|
||||
await RelayController.getInstance().fetchEventFromUserRelays(
|
||||
filter,
|
||||
userHexKey,
|
||||
UserRelaysType.Both
|
||||
)
|
||||
const contactListEvent = await fetchEventFromUserRelays(
|
||||
filter,
|
||||
userHexKey,
|
||||
UserRelaysType.Both
|
||||
)
|
||||
|
||||
if (
|
||||
!contactListEvent ||
|
||||
|
@ -10,7 +10,7 @@ import React, {
|
||||
import Countdown, { CountdownRenderProps } from 'react-countdown'
|
||||
import { toast } from 'react-toastify'
|
||||
import { MetadataController, ZapController } from '../controllers'
|
||||
import { useAppSelector, useDidMount } from '../hooks'
|
||||
import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
|
||||
import '../styles/popup.css'
|
||||
import { PaymentRequest, UserProfile } from '../types'
|
||||
import {
|
||||
@ -133,9 +133,11 @@ export const ZapQR = React.memo(
|
||||
setTotalZapAmount,
|
||||
setHasZapped
|
||||
}: ZapQRProps) => {
|
||||
const { ndk } = useNDKContext()
|
||||
|
||||
useDidMount(() => {
|
||||
ZapController.getInstance()
|
||||
.pollZapReceipt(paymentRequest)
|
||||
.pollZapReceipt(paymentRequest, ndk)
|
||||
.then((zapReceipt) => {
|
||||
toast.success(`Successfully sent sats!`)
|
||||
if (setTotalZapAmount) {
|
||||
@ -249,6 +251,7 @@ export const ZapPopUp = ({
|
||||
setHasZapped,
|
||||
handleClose
|
||||
}: ZapPopUpProps) => {
|
||||
const { findMetadata } = useNDKContext()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||
const [amount, setAmount] = useState<number>(0)
|
||||
@ -282,9 +285,8 @@ export const ZapPopUp = ({
|
||||
}
|
||||
|
||||
setLoadingSpinnerDesc('finding receiver metadata')
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
|
||||
const receiverMetadata = await metadataController.findMetadata(receiver)
|
||||
const receiverMetadata = await findMetadata(receiver)
|
||||
|
||||
if (!receiverMetadata?.lud16) {
|
||||
setIsLoading(false)
|
||||
@ -480,6 +482,7 @@ export const ZapSplit = ({
|
||||
setHasZapped,
|
||||
handleClose
|
||||
}: ZapSplitProps) => {
|
||||
const { findMetadata } = useNDKContext()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||
const [amount, setAmount] = useState<number>(0)
|
||||
@ -495,12 +498,12 @@ export const ZapSplit = ({
|
||||
const [invoices, setInvoices] = useState<Map<string, PaymentRequest>>()
|
||||
|
||||
useDidMount(async () => {
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
metadataController.findMetadata(pubkey).then((res) => {
|
||||
findMetadata(pubkey).then((res) => {
|
||||
setAuthor(res)
|
||||
})
|
||||
|
||||
metadataController.findAdminMetadata().then((res) => {
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
findMetadata(metadataController.adminNpubs[0]).then((res) => {
|
||||
setAdmin(res)
|
||||
})
|
||||
})
|
||||
|
@ -4,16 +4,19 @@ import NDK, {
|
||||
NDKFilter,
|
||||
NDKKind,
|
||||
NDKRelaySet,
|
||||
NDKSubscriptionCacheUsage
|
||||
NDKSubscriptionCacheUsage,
|
||||
NDKUser
|
||||
} 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 } from 'types'
|
||||
import { ModDetails, UserProfile } from 'types'
|
||||
import {
|
||||
constructModListFromEvents,
|
||||
hexToNpub,
|
||||
log,
|
||||
LogType,
|
||||
npubToHex,
|
||||
@ -31,6 +34,22 @@ interface NDKContextType {
|
||||
ndk: NDK
|
||||
fetchMods: (opts: FetchModsOptions) => Promise<ModDetails[]>
|
||||
fetchEvents: (filter: NDKFilter, relayUrls?: string[]) => Promise<NDKEvent[]>
|
||||
fetchEvent: (
|
||||
filter: NDKFilter,
|
||||
relayUrls?: string[]
|
||||
) => Promise<NDKEvent | null>
|
||||
|
||||
fetchEventsFromUserRelays: (
|
||||
filter: NDKFilter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
) => Promise<NDKEvent[]>
|
||||
fetchEventFromUserRelays: (
|
||||
filter: NDKFilter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
) => Promise<NDKEvent | null>
|
||||
findMetadata: (pubkey: string) => Promise<UserProfile>
|
||||
}
|
||||
|
||||
// Create the context with an initial value of `null`
|
||||
@ -56,6 +75,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const ndk = useMemo(() => {
|
||||
localStorage.setItem('debug', '*')
|
||||
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'degmod-db' })
|
||||
dexieAdapter.locking = true
|
||||
const ndk = new NDK({
|
||||
enableOutboxModel: true,
|
||||
autoConnectUserRelays: true,
|
||||
@ -158,6 +178,14 @@ 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.
|
||||
*
|
||||
* @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[] = []
|
||||
@ -207,19 +235,112 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
})
|
||||
.catch((err) => {
|
||||
// Log the error and show a notification if fetching fails
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
'An error occurred in fetching mods from relays',
|
||||
err
|
||||
)
|
||||
toast.error('An error occurred in fetching mods from relays') // Show error notification
|
||||
log(true, LogType.Error, 'An error occurred in fetching events', err)
|
||||
toast.error('An error occurred in fetching events') // Show error notification
|
||||
return [] // Return an empty array in case of an error
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieves an event from a set of relays based on a provided filter.
|
||||
* If no relays are specified, it defaults to using connected relays.
|
||||
*
|
||||
* @param filter - The filter criteria to find the event.
|
||||
* @param relaysUrls - An optional array of relay URLs to search for the event.
|
||||
* @returns Returns a promise that resolves to the found event or null if not found.
|
||||
*/
|
||||
const fetchEvent = async (filter: NDKFilter, relayUrls: string[] = []) => {
|
||||
const events = await fetchEvents(filter, relayUrls)
|
||||
if (events.length === 0) return null
|
||||
return events[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieves multiple events from the user's relays based on a specified filter.
|
||||
* The function first retrieves the user's relays, and then fetches the events using the provided filter.
|
||||
*
|
||||
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
||||
* @param hexKey - The hexadecimal representation of the user's public key.
|
||||
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
||||
* @returns A promise that resolves with an array of events.
|
||||
*/
|
||||
const fetchEventsFromUserRelays = async (
|
||||
filter: NDKFilter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
) => {
|
||||
// Find the user's relays.
|
||||
const relayUrls = await getRelayListForUser(hexKey, ndk)
|
||||
.then((ndkRelayList) => {
|
||||
if (ndkRelayList) return ndkRelayList[userRelaysType]
|
||||
return [] // Return an empty array if ndkRelayList is undefined
|
||||
})
|
||||
.catch((err) => {
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
`An error occurred in fetching user's (${hexKey}) ${userRelaysType}`,
|
||||
err
|
||||
)
|
||||
return [] as string[]
|
||||
})
|
||||
|
||||
// Fetch the event from the user's relays using the provided filter and relay URLs
|
||||
return fetchEvents(filter, relayUrls)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an event from the user's relays based on a specified filter.
|
||||
* The function first retrieves the user's relays, and then fetches the event using the provided filter.
|
||||
*
|
||||
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
||||
* @param hexKey - The hexadecimal representation of the user's public key.
|
||||
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
||||
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
||||
*/
|
||||
const fetchEventFromUserRelays = async (
|
||||
filter: NDKFilter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
) => {
|
||||
const events = await fetchEventsFromUserRelays(
|
||||
filter,
|
||||
hexKey,
|
||||
userRelaysType
|
||||
)
|
||||
if (events.length === 0) return null
|
||||
return events[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds metadata for a given pubkey.
|
||||
*
|
||||
* @param hexKey - The pubkey to search for metadata.
|
||||
* @returns A promise that resolves to the metadata event.
|
||||
*/
|
||||
const findMetadata = async (pubkey: string): Promise<UserProfile> => {
|
||||
const npub = hexToNpub(pubkey)
|
||||
|
||||
const user = new NDKUser({ npub })
|
||||
user.ndk = ndk
|
||||
|
||||
const userProfile = await user.fetchProfile()
|
||||
|
||||
return userProfile
|
||||
}
|
||||
|
||||
return (
|
||||
<NDKContext.Provider value={{ ndk, fetchMods, fetchEvents }}>
|
||||
<NDKContext.Provider
|
||||
value={{
|
||||
ndk,
|
||||
fetchMods,
|
||||
fetchEvents,
|
||||
fetchEvent,
|
||||
fetchEventsFromUserRelays,
|
||||
fetchEventFromUserRelays,
|
||||
findMetadata
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</NDKContext.Provider>
|
||||
)
|
||||
|
@ -1,8 +1,7 @@
|
||||
import NDK, { getRelayListForUser, NDKList, NDKUser } from '@nostr-dev-kit/ndk'
|
||||
import NDK, { getRelayListForUser, NDKList } from '@nostr-dev-kit/ndk'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import { MuteLists } from '../types'
|
||||
import { UserProfile } from '../types/user'
|
||||
import { hexToNpub, log, LogType, npubToHex, timeout } from '../utils'
|
||||
import { log, LogType, npubToHex, timeout } from '../utils'
|
||||
|
||||
export enum UserRelaysType {
|
||||
Read = 'readRelayUrls',
|
||||
@ -16,7 +15,6 @@ export enum UserRelaysType {
|
||||
export class MetadataController {
|
||||
private static instance: MetadataController
|
||||
private ndk: NDK
|
||||
private usersMetadata = new Map<string, UserProfile>()
|
||||
public adminNpubs: string[]
|
||||
public adminRelays = new Set<string>()
|
||||
public reportingNpub: string
|
||||
@ -84,40 +82,6 @@ export class MetadataController {
|
||||
return MetadataController.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds metadata for a given pubkey.
|
||||
*
|
||||
* @param hexKey - The pubkey to search for metadata.
|
||||
* @returns A promise that resolves to the metadata event.
|
||||
*/
|
||||
public findMetadata = async (pubkey: string): Promise<UserProfile> => {
|
||||
const npub = hexToNpub(pubkey)
|
||||
|
||||
const cachedMetadata = this.usersMetadata.get(npub)
|
||||
if (cachedMetadata) {
|
||||
return cachedMetadata
|
||||
}
|
||||
|
||||
const user = new NDKUser({ npub })
|
||||
user.ndk = this.ndk
|
||||
|
||||
const userProfile = await user.fetchProfile()
|
||||
if (userProfile) {
|
||||
this.usersMetadata.set(npub, userProfile)
|
||||
}
|
||||
|
||||
return userProfile
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds metadata for admin user.
|
||||
*
|
||||
* @returns A promise that resolves to the metadata event.
|
||||
*/
|
||||
public findAdminMetadata = async (): Promise<UserProfile> => {
|
||||
return this.findMetadata(this.adminNpubs[0])
|
||||
}
|
||||
|
||||
public findUserRelays = async (
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType = UserRelaysType.Both
|
||||
|
@ -372,217 +372,6 @@ export class RelayController {
|
||||
return publishedOnRelays
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieves multiple event from a set of relays based on a provided filter.
|
||||
* If no relays are specified, it defaults to using connected relays.
|
||||
*
|
||||
* @param {Filter} filter - The filter criteria to find the event.
|
||||
* @param {string[]} [relays] - An optional array of relay URLs to search for the event.
|
||||
* @returns {Promise<Event | null>} - Returns a promise that resolves to the found event or null if not found.
|
||||
*/
|
||||
fetchEvents = async (
|
||||
filter: Filter,
|
||||
relayUrls: string[] = []
|
||||
): Promise<Event[]> => {
|
||||
const relaySet = new Set<string>()
|
||||
|
||||
// add all the relays passed to relay set
|
||||
relayUrls.forEach((relayUrl) => {
|
||||
relaySet.add(relayUrl)
|
||||
})
|
||||
|
||||
relaySet.add(import.meta.env.VITE_APP_RELAY)
|
||||
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
// add admin relays to relays array
|
||||
metadataController.adminRelays.forEach((relayUrl) => {
|
||||
relaySet.add(relayUrl)
|
||||
})
|
||||
|
||||
relayUrls = Array.from(relaySet)
|
||||
|
||||
// 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<Relay[]>((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 events: Event[] = []
|
||||
const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates
|
||||
|
||||
// Create a promise for each relay subscription
|
||||
const subPromises = relays.map((relay) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
// Subscribe to the relay with the specified filter
|
||||
const sub = relay.subscribe([filter], {
|
||||
// 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
|
||||
events.push(e) // Add the event to the array
|
||||
}
|
||||
},
|
||||
// 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)
|
||||
|
||||
// It is possible that different relays will send different events and events array may contain more events then specified limit in filter
|
||||
// To fix this issue we'll first sort these events and then return only limited events
|
||||
if (filter.limit) {
|
||||
// Sort events by creation date in descending order
|
||||
events.sort((a, b) => b.created_at - a.created_at)
|
||||
|
||||
return events.slice(0, filter.limit)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieves an event from a set of relays based on a provided filter.
|
||||
* If no relays are specified, it defaults to using connected relays.
|
||||
*
|
||||
* @param {Filter} filter - The filter criteria to find the event.
|
||||
* @param {string[]} [relays] - An optional array of relay URLs to search for the event.
|
||||
* @returns {Promise<Event | null>} - Returns a promise that resolves to the found event or null if not found.
|
||||
*/
|
||||
fetchEvent = async (
|
||||
filter: Filter,
|
||||
relays: string[] = []
|
||||
): Promise<Event | null> => {
|
||||
// first check if event is present in cached map then return that
|
||||
// otherwise query relays
|
||||
if (filter['#a']) {
|
||||
const aTag = filter['#a'][0]
|
||||
const cachedEvent = this.events.get(aTag)
|
||||
|
||||
if (cachedEvent) return cachedEvent
|
||||
}
|
||||
|
||||
const events = await this.fetchEvents(filter, relays)
|
||||
|
||||
// Sort events by creation date in descending order
|
||||
events.sort((a, b) => b.created_at - a.created_at)
|
||||
|
||||
if (events.length > 0) {
|
||||
const event = events[0]
|
||||
|
||||
// if the aTag was specified in filter then cache the fetched event before returning
|
||||
if (filter['#a']) {
|
||||
const aTag = filter['#a'][0]
|
||||
this.events.set(aTag, event)
|
||||
}
|
||||
|
||||
// return the event
|
||||
return event
|
||||
}
|
||||
|
||||
// return null if event array is empty
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieves multiple events from the user's relays based on a specified filter.
|
||||
* The function first retrieves the user's relays, and then fetches the events using the provided filter.
|
||||
*
|
||||
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
||||
* @param hexKey - The hexadecimal representation of the user's public key.
|
||||
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
||||
* @returns A promise that resolves with an array of events.
|
||||
*/
|
||||
fetchEventsFromUserRelays = async (
|
||||
filter: Filter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
): Promise<Event[]> => {
|
||||
// Get an instance of the MetadataController, which manages user metadata and relays
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
|
||||
// Find the user's relays using the MetadataController.
|
||||
const relayUrls = await metadataController.findUserRelays(
|
||||
hexKey,
|
||||
userRelaysType
|
||||
)
|
||||
|
||||
// Fetch the event from the user's relays using the provided filter and relay URLs
|
||||
return this.fetchEvents(filter, relayUrls)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an event from the user's relays based on a specified filter.
|
||||
* The function first retrieves the user's relays, and then fetches the event using the provided filter.
|
||||
*
|
||||
* @param filter - The event filter to use when fetching the event (e.g., kinds, authors).
|
||||
* @param hexKey - The hexadecimal representation of the user's public key.
|
||||
* @param userRelaysType - The type of relays to search (e.g., write, read).
|
||||
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
||||
*/
|
||||
fetchEventFromUserRelays = async (
|
||||
filter: Filter,
|
||||
hexKey: string,
|
||||
userRelaysType: UserRelaysType
|
||||
): Promise<Event | null> => {
|
||||
// first check if event is present in cached map then return that
|
||||
// otherwise query relays
|
||||
if (filter['#a']) {
|
||||
const aTag = filter['#a'][0]
|
||||
const cachedEvent = this.events.get(aTag)
|
||||
|
||||
if (cachedEvent) return cachedEvent
|
||||
}
|
||||
|
||||
const events = await this.fetchEventsFromUserRelays(
|
||||
filter,
|
||||
hexKey,
|
||||
userRelaysType
|
||||
)
|
||||
// Sort events by creation date in descending order
|
||||
events.sort((a, b) => b.created_at - a.created_at)
|
||||
|
||||
if (events.length > 0) {
|
||||
const event = events[0]
|
||||
|
||||
// if the aTag was specified in filter then cache the fetched event before returning
|
||||
if (filter['#a']) {
|
||||
const aTag = filter['#a'][0]
|
||||
this.events.set(aTag, event)
|
||||
}
|
||||
|
||||
// return the event
|
||||
return event
|
||||
}
|
||||
|
||||
// return null if event array is empty
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to events from multiple relays.
|
||||
*
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { Invoice } from '@getalby/lightning-tools'
|
||||
import NDK, {
|
||||
NDKFilter,
|
||||
NDKKind,
|
||||
NDKRelaySet,
|
||||
NDKSubscriptionCacheUsage
|
||||
} from '@nostr-dev-kit/ndk'
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
import { Filter, kinds } from 'nostr-tools'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import { requestProvider, SendPaymentResponse, WebLNProvider } from 'webln'
|
||||
import {
|
||||
isLnurlResponse,
|
||||
@ -11,7 +17,6 @@ import {
|
||||
ZapRequest
|
||||
} from '../types'
|
||||
import { log, LogType, npubToHex } from '../utils'
|
||||
import { RelayController } from './relay'
|
||||
import { MetadataController, UserRelaysType } from './metadata'
|
||||
|
||||
/**
|
||||
@ -134,6 +139,7 @@ export class ZapController {
|
||||
*/
|
||||
async pollZapReceipt(
|
||||
paymentRequest: PaymentRequest,
|
||||
ndk: NDK,
|
||||
pollingTimeout?: number
|
||||
) {
|
||||
const { pr, ...zapRequest } = paymentRequest
|
||||
@ -148,7 +154,7 @@ export class ZapController {
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeout)
|
||||
|
||||
subscriptions.forEach((subscription) => subscription.close())
|
||||
subscription.stop()
|
||||
}
|
||||
|
||||
// Polling timeout
|
||||
@ -168,32 +174,35 @@ export class ZapController {
|
||||
const relayUrls = relaysTag.slice(1)
|
||||
|
||||
// filter relay for event of kind 9735
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Zap],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Zap],
|
||||
since: created_at
|
||||
}
|
||||
|
||||
const subscriptions =
|
||||
await RelayController.getInstance().subscribeForEvents(
|
||||
filter,
|
||||
relayUrls,
|
||||
async (event) => {
|
||||
// get description tag of the event
|
||||
const description = event.tags.filter(
|
||||
(tag) => tag[0] === 'description'
|
||||
)[0]
|
||||
const subscription = ndk.subscribe(
|
||||
filter,
|
||||
{
|
||||
closeOnEose: false,
|
||||
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY
|
||||
},
|
||||
NDKRelaySet.fromRelayUrls(relayUrls, ndk, true)
|
||||
)
|
||||
|
||||
// compare description tag of the event with stringified zap request
|
||||
if (description[1] === zapRequestStringified) {
|
||||
// validate zap receipt
|
||||
if (await this.validateZapReceipt(pr, event as ZapReceipt)) {
|
||||
cleanup()
|
||||
subscription.on('event', async (ndkEvent) => {
|
||||
// compare description tag of the event with stringified zap request
|
||||
if (ndkEvent.tagValue('description') === zapRequestStringified) {
|
||||
// validate zap receipt
|
||||
if (
|
||||
await this.validateZapReceipt(pr, ndkEvent.rawEvent() as ZapReceipt)
|
||||
) {
|
||||
cleanup()
|
||||
|
||||
resolve(event as ZapReceipt)
|
||||
}
|
||||
}
|
||||
resolve(ndkEvent.rawEvent() as ZapReceipt)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
subscription.start()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,43 +1,87 @@
|
||||
import {
|
||||
MetadataController,
|
||||
RelayController,
|
||||
UserRelaysType
|
||||
} from 'controllers'
|
||||
import { Filter, kinds } from 'nostr-tools'
|
||||
import { useState } from 'react'
|
||||
getRelayListForUser,
|
||||
NDKFilter,
|
||||
NDKKind,
|
||||
NDKRelaySet,
|
||||
NDKSubscription,
|
||||
NDKSubscriptionCacheUsage
|
||||
} from '@nostr-dev-kit/ndk'
|
||||
import { UserRelaysType } from 'controllers'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { CommentEvent, ModDetails } from 'types'
|
||||
import { useDidMount } from './useDidMount'
|
||||
import { log, LogType } from 'utils'
|
||||
import { useNDKContext } from './useNDKContext'
|
||||
|
||||
export const useComments = (mod: ModDetails) => {
|
||||
const { ndk } = useNDKContext()
|
||||
const [commentEvents, setCommentEvents] = useState<CommentEvent[]>([])
|
||||
|
||||
useDidMount(async () => {
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
useEffect(() => {
|
||||
let subscription: NDKSubscription // Define the subscription variable here for cleanup
|
||||
|
||||
const authorReadRelays = await metadataController.findUserRelays(
|
||||
mod.author,
|
||||
UserRelaysType.Read
|
||||
)
|
||||
const setupSubscription = async () => {
|
||||
// Find the mod author's relays.
|
||||
const authorReadRelays = await getRelayListForUser(mod.author, ndk)
|
||||
.then((ndkRelayList) => {
|
||||
if (ndkRelayList) return ndkRelayList[UserRelaysType.Read]
|
||||
return [] // Return an empty array if ndkRelayList is undefined
|
||||
})
|
||||
.catch((err) => {
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
`An error occurred in fetching user's (${mod.author}) ${UserRelaysType.Read}`,
|
||||
err
|
||||
)
|
||||
return [] as string[]
|
||||
})
|
||||
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.ShortTextNote],
|
||||
'#a': [mod.aTag]
|
||||
}
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Text],
|
||||
'#a': [mod.aTag]
|
||||
}
|
||||
|
||||
RelayController.getInstance().subscribeForEvents(
|
||||
filter,
|
||||
authorReadRelays,
|
||||
(event) => {
|
||||
subscription = ndk.subscribe(
|
||||
filter,
|
||||
{
|
||||
closeOnEose: false,
|
||||
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST
|
||||
},
|
||||
NDKRelaySet.fromRelayUrls(authorReadRelays, ndk, true)
|
||||
)
|
||||
|
||||
subscription.on('event', (ndkEvent) => {
|
||||
setCommentEvents((prev) => {
|
||||
if (prev.find((e) => e.id === event.id)) {
|
||||
if (prev.find((e) => e.id === ndkEvent.id)) {
|
||||
return [...prev]
|
||||
}
|
||||
|
||||
return [event, ...prev]
|
||||
const commentEvent: CommentEvent = {
|
||||
kind: NDKKind.Text,
|
||||
tags: ndkEvent.tags,
|
||||
content: ndkEvent.content,
|
||||
created_at: ndkEvent.created_at!,
|
||||
pubkey: ndkEvent.pubkey,
|
||||
id: ndkEvent.id,
|
||||
sig: ndkEvent.sig!
|
||||
}
|
||||
|
||||
return [commentEvent, ...prev]
|
||||
})
|
||||
})
|
||||
|
||||
subscription.start()
|
||||
}
|
||||
|
||||
setupSubscription()
|
||||
|
||||
// Cleanup function to stop the subscription on unmount
|
||||
return () => {
|
||||
if (subscription) {
|
||||
subscription.stop()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}, [mod.aTag, mod.author, ndk])
|
||||
|
||||
return {
|
||||
commentEvents,
|
||||
|
@ -9,11 +9,23 @@ export const useNDKContext = () => {
|
||||
'NDKContext should not be used in out component tree hierarchy'
|
||||
)
|
||||
|
||||
const { ndk, fetchEvents, fetchMods } = ndkContext
|
||||
const {
|
||||
ndk,
|
||||
fetchEvents,
|
||||
fetchEvent,
|
||||
fetchEventsFromUserRelays,
|
||||
fetchEventFromUserRelays,
|
||||
fetchMods,
|
||||
findMetadata
|
||||
} = ndkContext
|
||||
|
||||
return {
|
||||
ndk,
|
||||
fetchEvents,
|
||||
fetchMods
|
||||
fetchEvent,
|
||||
fetchEventsFromUserRelays,
|
||||
fetchEventFromUserRelays,
|
||||
fetchMods,
|
||||
findMetadata
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk'
|
||||
import { REACTIONS } from 'constants.ts'
|
||||
import { RelayController, UserRelaysType } from 'controllers'
|
||||
import { useAppSelector, useDidMount } from 'hooks'
|
||||
import { Event, Filter, UnsignedEvent, kinds } from 'nostr-tools'
|
||||
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
|
||||
import { Event, kinds, UnsignedEvent } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { abbreviateNumber, log, LogType, now } from 'utils'
|
||||
|
||||
type UseReactionsParams = {
|
||||
@ -13,14 +14,15 @@ type UseReactionsParams = {
|
||||
}
|
||||
|
||||
export const useReactions = (params: UseReactionsParams) => {
|
||||
const { ndk, fetchEventsFromUserRelays } = useNDKContext()
|
||||
const [isReactionInProgress, setIsReactionInProgress] = useState(false)
|
||||
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
||||
const [reactionEvents, setReactionEvents] = useState<Event[]>([])
|
||||
const [reactionEvents, setReactionEvents] = useState<NDKEvent[]>([])
|
||||
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
|
||||
useDidMount(() => {
|
||||
const filter: Filter = {
|
||||
const filter: NDKFilter = {
|
||||
kinds: [kinds.Reaction]
|
||||
}
|
||||
|
||||
@ -30,8 +32,7 @@ export const useReactions = (params: UseReactionsParams) => {
|
||||
filter['#e'] = [params.eTag]
|
||||
}
|
||||
|
||||
RelayController.getInstance()
|
||||
.fetchEventsFromUserRelays(filter, params.pubkey, UserRelaysType.Read)
|
||||
fetchEventsFromUserRelays(filter, params.pubkey, UserRelaysType.Read)
|
||||
.then((events) => {
|
||||
setReactionEvents(events)
|
||||
})
|
||||
@ -118,7 +119,7 @@ export const useReactions = (params: UseReactionsParams) => {
|
||||
|
||||
if (!signedEvent) return
|
||||
|
||||
setReactionEvents((prev) => [...prev, signedEvent])
|
||||
setReactionEvents((prev) => [...prev, new NDKEvent(ndk, signedEvent)])
|
||||
|
||||
const publishedOnRelays = await RelayController.getInstance().publish(
|
||||
signedEvent as Event,
|
||||
|
@ -7,7 +7,12 @@ import { Link } from 'react-router-dom'
|
||||
import { Banner } from '../components/Banner'
|
||||
import { ZapPopUp } from '../components/Zap'
|
||||
import { MetadataController } from '../controllers'
|
||||
import { useAppDispatch, useAppSelector, useDidMount } from '../hooks'
|
||||
import {
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
useDidMount,
|
||||
useNDKContext
|
||||
} from '../hooks'
|
||||
import { appRoutes } from '../routes'
|
||||
import { setAuth, setUser } from '../store/reducers/user'
|
||||
import mainStyles from '../styles//main.module.scss'
|
||||
@ -17,6 +22,7 @@ import { npubToHex } from '../utils'
|
||||
|
||||
export const Header = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const { findMetadata } = useNDKContext()
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
|
||||
useEffect(() => {
|
||||
@ -41,23 +47,21 @@ export const Header = () => {
|
||||
pubkey: npubToHex(npub)!
|
||||
})
|
||||
)
|
||||
MetadataController.getInstance().then((metadataController) => {
|
||||
metadataController.findMetadata(npub).then((userProfile) => {
|
||||
if (userProfile) {
|
||||
dispatch(
|
||||
setUser({
|
||||
npub,
|
||||
pubkey: npubToHex(npub)!,
|
||||
...userProfile
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
findMetadata(npub).then((userProfile) => {
|
||||
if (userProfile) {
|
||||
dispatch(
|
||||
setUser({
|
||||
npub,
|
||||
pubkey: npubToHex(npub)!,
|
||||
...userProfile
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [dispatch])
|
||||
}, [dispatch, findMetadata])
|
||||
|
||||
const handleLogin = () => {
|
||||
launchNostrLoginDialog()
|
||||
@ -357,8 +361,14 @@ const RegisterButtonWithDialog = () => {
|
||||
</label>
|
||||
<p className='labelDescriptionMain'>
|
||||
Once you create your "account" on any of these (
|
||||
<a href="https://video.nostr.build/765aa9bf16dd58bca701efee2572f7e77f29b2787cddd2bee8bbbdea35798153.mp4" target="blank_">Here's a quick video guide</a>
|
||||
), come back and click login, then sign-in with extension.
|
||||
<a
|
||||
href='https://video.nostr.build/765aa9bf16dd58bca701efee2572f7e77f29b2787cddd2bee8bbbdea35798153.mp4'
|
||||
target='blank_'
|
||||
>
|
||||
Here's a quick video guide
|
||||
</a>
|
||||
), come back and click login, then sign-in with
|
||||
extension.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
|
@ -1,20 +1,21 @@
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||
import {
|
||||
NDKFilter,
|
||||
NDKKind,
|
||||
NDKSubscriptionCacheUsage
|
||||
} from '@nostr-dev-kit/ndk'
|
||||
import { ModCard } from 'components/ModCard'
|
||||
import { ModFilter } from 'components/ModsFilter'
|
||||
import { PaginationWithPageNumbers } from 'components/Pagination'
|
||||
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
|
||||
import { RelayController } from 'controllers'
|
||||
import {
|
||||
useAppSelector,
|
||||
useFilteredMods,
|
||||
useMuteLists,
|
||||
useNDKContext,
|
||||
useNSFWList
|
||||
} from 'hooks'
|
||||
import { Filter, kinds } from 'nostr-tools'
|
||||
import { Subscription } from 'nostr-tools/abstract-relay'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import {
|
||||
FilterOptions,
|
||||
ModDetails,
|
||||
@ -22,11 +23,12 @@ import {
|
||||
NSFWFilter,
|
||||
SortBy
|
||||
} from 'types'
|
||||
import { extractModData, isModDataComplete, log, LogType } from 'utils'
|
||||
import { extractModData, isModDataComplete } from 'utils'
|
||||
|
||||
export const GamePage = () => {
|
||||
const params = useParams()
|
||||
const { name: gameName } = params
|
||||
const { ndk } = useNDKContext()
|
||||
const muteLists = useMuteLists()
|
||||
const nsfwList = useNSFWList()
|
||||
|
||||
@ -38,8 +40,6 @@ export const GamePage = () => {
|
||||
})
|
||||
const [mods, setMods] = useState<ModDetails[]>([])
|
||||
|
||||
const hasEffectRun = useRef(false)
|
||||
const [isSubscribing, setIsSubscribing] = useState(false)
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
@ -66,57 +66,40 @@ export const GamePage = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (hasEffectRun.current) {
|
||||
return
|
||||
}
|
||||
|
||||
hasEffectRun.current = true // Set it so the effect doesn't run again
|
||||
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.ClassifiedListing],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Classified],
|
||||
'#t': [T_TAG_VALUE]
|
||||
}
|
||||
|
||||
setIsSubscribing(true)
|
||||
const subscription = ndk.subscribe(filter, {
|
||||
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
|
||||
closeOnEose: true
|
||||
})
|
||||
|
||||
let subscriptions: Subscription[] = []
|
||||
subscription.on('event', (ndkEvent) => {
|
||||
if (isModDataComplete(ndkEvent)) {
|
||||
const mod = extractModData(ndkEvent)
|
||||
if (mod.game === gameName)
|
||||
setMods((prev) => {
|
||||
if (prev.find((e) => e.aTag === mod.aTag)) return [...prev]
|
||||
|
||||
RelayController.getInstance()
|
||||
.subscribeForEvents(filter, [], (event) => {
|
||||
if (isModDataComplete(event)) {
|
||||
const mod = extractModData(event)
|
||||
if (mod.game === gameName) setMods((prev) => [...prev, mod])
|
||||
}
|
||||
})
|
||||
.then((subs) => {
|
||||
subscriptions = subs
|
||||
})
|
||||
.catch((err) => {
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
'An error occurred in subscribing to relays.',
|
||||
err
|
||||
)
|
||||
toast.error(err.message || err)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsSubscribing(false)
|
||||
})
|
||||
return [...prev, mod]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup function to stop all subscriptions
|
||||
subscription.start()
|
||||
|
||||
// Cleanup function to stop subscription
|
||||
return () => {
|
||||
subscriptions.forEach((sub) => sub.close()) // close each subscription
|
||||
subscription.stop()
|
||||
}
|
||||
}, [gameName])
|
||||
}, [gameName, ndk])
|
||||
|
||||
if (!gameName) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSubscribing && (
|
||||
<LoadingSpinner desc='Subscribing to relays for mods' />
|
||||
)}
|
||||
<div className='InnerBodyMain'>
|
||||
<div className='ContainerMain'>
|
||||
<div className='IBMSecMainGroup IBMSecMainGroupAlt'>
|
||||
|
@ -146,7 +146,7 @@ type SlideContentProps = {
|
||||
|
||||
const SlideContent = ({ naddr }: SlideContentProps) => {
|
||||
const navigate = useNavigate()
|
||||
const { fetchEvents } = useNDKContext()
|
||||
const { fetchEvent } = useNDKContext()
|
||||
const [mod, setMod] = useState<ModDetails>()
|
||||
|
||||
useDidMount(() => {
|
||||
@ -159,10 +159,9 @@ const SlideContent = ({ naddr }: SlideContentProps) => {
|
||||
kinds: [kind]
|
||||
}
|
||||
|
||||
fetchEvents(ndkFilter, relays)
|
||||
.then((ndkEvents) => {
|
||||
if (ndkEvents.length > 0) {
|
||||
const ndkEvent = ndkEvents[0]
|
||||
fetchEvent(ndkFilter, relays)
|
||||
.then((ndkEvent) => {
|
||||
if (ndkEvent) {
|
||||
const extracted = extractModData(ndkEvent)
|
||||
setMod(extracted)
|
||||
}
|
||||
@ -221,7 +220,7 @@ type DisplayModProps = {
|
||||
const DisplayMod = ({ naddr }: DisplayModProps) => {
|
||||
const [mod, setMod] = useState<ModDetails>()
|
||||
|
||||
const { fetchEvents } = useNDKContext()
|
||||
const { fetchEvent } = useNDKContext()
|
||||
|
||||
useDidMount(() => {
|
||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||
@ -233,10 +232,9 @@ const DisplayMod = ({ naddr }: DisplayModProps) => {
|
||||
kinds: [kind]
|
||||
}
|
||||
|
||||
fetchEvents(ndkFilter, relays)
|
||||
.then((ndkEvents) => {
|
||||
if (ndkEvents.length > 0) {
|
||||
const ndkEvent = ndkEvents[0]
|
||||
fetchEvent(ndkFilter, relays)
|
||||
.then((ndkEvent) => {
|
||||
if (ndkEvent) {
|
||||
const extracted = extractModData(ndkEvent)
|
||||
setMod(extracted)
|
||||
}
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { NDKFilter, NDKKind } from '@nostr-dev-kit/ndk'
|
||||
import Link from '@tiptap/extension-link'
|
||||
import { EditorContent, useEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { formatDate } from 'date-fns'
|
||||
import FsLightbox from 'fslightbox-react'
|
||||
import { Filter, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||
import { nip19, UnsignedEvent } from 'nostr-tools'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Link as ReactRouterLink, useParams } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { BlogCard } from '../../components/BlogCard'
|
||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||
import { ProfileSection } from '../../components/ProfileSection'
|
||||
import {
|
||||
MetadataController,
|
||||
RelayController,
|
||||
UserRelaysType
|
||||
} from '../../controllers'
|
||||
import { useAppSelector, useDidMount } from '../../hooks'
|
||||
import { MetadataController, UserRelaysType } from '../../controllers'
|
||||
import { useAppSelector, useDidMount, useNDKContext } from '../../hooks'
|
||||
import { getGamePageRoute, getModsEditPageRoute } from '../../routes'
|
||||
import '../../styles/comments.css'
|
||||
import '../../styles/downloads.css'
|
||||
@ -47,6 +44,7 @@ import { Zap } from './internal/zap'
|
||||
|
||||
export const ModPage = () => {
|
||||
const { naddr } = useParams()
|
||||
const { fetchEvent } = useNDKContext()
|
||||
const [modData, setModData] = useState<ModDetails>()
|
||||
const [isFetching, setIsFetching] = useState(true)
|
||||
const [commentCount, setCommentCount] = useState(0)
|
||||
@ -56,14 +54,13 @@ export const ModPage = () => {
|
||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||
const { identifier, kind, pubkey, relays = [] } = decoded.data
|
||||
|
||||
const filter: Filter = {
|
||||
const filter: NDKFilter = {
|
||||
'#a': [identifier],
|
||||
authors: [pubkey],
|
||||
kinds: [kind]
|
||||
}
|
||||
|
||||
RelayController.getInstance()
|
||||
.fetchEvent(filter, relays)
|
||||
fetchEvent(filter, relays)
|
||||
.then((event) => {
|
||||
if (event) {
|
||||
const extracted = extractModData(event)
|
||||
@ -214,6 +211,8 @@ type GameProps = {
|
||||
}
|
||||
|
||||
const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
const { fetchEventFromUserRelays } = useNDKContext()
|
||||
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||
@ -225,56 +224,54 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
if (userState.auth && userState.user?.pubkey) {
|
||||
const pubkey = userState.user.pubkey as string
|
||||
|
||||
const muteListFilter: Filter = {
|
||||
kinds: [kinds.Mutelist],
|
||||
const muteListFilter: NDKFilter = {
|
||||
kinds: [NDKKind.MuteList],
|
||||
authors: [pubkey]
|
||||
}
|
||||
|
||||
RelayController.getInstance()
|
||||
.fetchEventFromUserRelays(muteListFilter, pubkey, UserRelaysType.Write)
|
||||
.then((event) => {
|
||||
if (event) {
|
||||
// get a list of tags
|
||||
const tags = event.tags
|
||||
const blocked =
|
||||
tags.findIndex((item) => item[0] === 'a' && item[1] === aTag) !==
|
||||
-1
|
||||
fetchEventFromUserRelays(
|
||||
muteListFilter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
).then((event) => {
|
||||
if (event) {
|
||||
// get a list of tags
|
||||
const tags = event.tags
|
||||
const blocked =
|
||||
tags.findIndex((item) => item[0] === 'a' && item[1] === aTag) !== -1
|
||||
|
||||
setIsBlocked(blocked)
|
||||
}
|
||||
})
|
||||
setIsBlocked(blocked)
|
||||
}
|
||||
})
|
||||
|
||||
if (
|
||||
userState.user.npub &&
|
||||
userState.user.npub === import.meta.env.VITE_REPORTING_NPUB
|
||||
) {
|
||||
const nsfwListFilter: Filter = {
|
||||
kinds: [kinds.Curationsets],
|
||||
const nsfwListFilter: NDKFilter = {
|
||||
kinds: [NDKKind.ArticleCurationSet],
|
||||
authors: [pubkey],
|
||||
'#d': ['nsfw']
|
||||
}
|
||||
|
||||
RelayController.getInstance()
|
||||
.fetchEventFromUserRelays(
|
||||
nsfwListFilter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
.then((event) => {
|
||||
if (event) {
|
||||
// get a list of tags
|
||||
const tags = event.tags
|
||||
const existsInNSFWList =
|
||||
tags.findIndex(
|
||||
(item) => item[0] === 'a' && item[1] === aTag
|
||||
) !== -1
|
||||
fetchEventFromUserRelays(
|
||||
nsfwListFilter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
).then((event) => {
|
||||
if (event) {
|
||||
// get a list of tags
|
||||
const tags = event.tags
|
||||
const existsInNSFWList =
|
||||
tags.findIndex((item) => item[0] === 'a' && item[1] === aTag) !==
|
||||
-1
|
||||
|
||||
setIsAddedToNSFW(existsInNSFWList)
|
||||
}
|
||||
})
|
||||
setIsAddedToNSFW(existsInNSFWList)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [userState, aTag])
|
||||
}, [userState, aTag, fetchEventFromUserRelays])
|
||||
|
||||
const handleBlock = async () => {
|
||||
let hexPubkey: string
|
||||
@ -298,18 +295,17 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
|
||||
// Define the event filter to search for the user's mute list events.
|
||||
// We look for events of a specific kind (Mutelist) authored by the given hexPubkey.
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Mutelist],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.MuteList],
|
||||
authors: [hexPubkey]
|
||||
}
|
||||
|
||||
// Fetch the mute list event from the relays. This returns the event containing the user's mute list.
|
||||
const muteListEvent =
|
||||
await RelayController.getInstance().fetchEventFromUserRelays(
|
||||
filter,
|
||||
hexPubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
const muteListEvent = await fetchEventFromUserRelays(
|
||||
filter,
|
||||
hexPubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
|
||||
let unsignedEvent: UnsignedEvent
|
||||
|
||||
@ -329,7 +325,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
|
||||
unsignedEvent = {
|
||||
pubkey: muteListEvent.pubkey,
|
||||
kind: muteListEvent.kind,
|
||||
kind: NDKKind.MuteList,
|
||||
content: muteListEvent.content,
|
||||
created_at: now(),
|
||||
tags: [...tags]
|
||||
@ -337,7 +333,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
} else {
|
||||
unsignedEvent = {
|
||||
pubkey: hexPubkey,
|
||||
kind: kinds.Mutelist,
|
||||
kind: NDKKind.MuteList,
|
||||
content: '',
|
||||
created_at: now(),
|
||||
tags: [['a', aTag]]
|
||||
@ -356,8 +352,8 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
const handleUnblock = async () => {
|
||||
const pubkey = userState.user?.pubkey as string
|
||||
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Mutelist],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.MuteList],
|
||||
authors: [pubkey]
|
||||
}
|
||||
|
||||
@ -365,12 +361,11 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
setLoadingSpinnerDesc(`Finding user's mute list`)
|
||||
|
||||
// Fetch the mute list event from the relays. This returns the event containing the user's mute list.
|
||||
const muteListEvent =
|
||||
await RelayController.getInstance().fetchEventFromUserRelays(
|
||||
filter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
const muteListEvent = await fetchEventFromUserRelays(
|
||||
filter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
|
||||
if (!muteListEvent) {
|
||||
toast.error(`Couldn't get user's mute list event from relays`)
|
||||
@ -381,7 +376,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
|
||||
const unsignedEvent: UnsignedEvent = {
|
||||
pubkey: muteListEvent.pubkey,
|
||||
kind: muteListEvent.kind,
|
||||
kind: NDKKind.MuteList,
|
||||
content: muteListEvent.content,
|
||||
created_at: now(),
|
||||
tags: tags.filter((item) => item[0] !== 'a' || item[1] !== aTag)
|
||||
@ -401,8 +396,8 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
|
||||
if (!pubkey) return
|
||||
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Curationsets],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.ArticleCurationSet],
|
||||
authors: [pubkey],
|
||||
'#d': ['nsfw']
|
||||
}
|
||||
@ -410,12 +405,11 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Finding NSFW list')
|
||||
|
||||
const nsfwListEvent =
|
||||
await RelayController.getInstance().fetchEventFromUserRelays(
|
||||
filter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
const nsfwListEvent = await fetchEventFromUserRelays(
|
||||
filter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
|
||||
let unsignedEvent: UnsignedEvent
|
||||
|
||||
@ -435,7 +429,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
|
||||
unsignedEvent = {
|
||||
pubkey: nsfwListEvent.pubkey,
|
||||
kind: nsfwListEvent.kind,
|
||||
kind: NDKKind.ArticleCurationSet,
|
||||
content: nsfwListEvent.content,
|
||||
created_at: now(),
|
||||
tags: [...tags]
|
||||
@ -443,7 +437,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
} else {
|
||||
unsignedEvent = {
|
||||
pubkey: pubkey,
|
||||
kind: kinds.Curationsets,
|
||||
kind: NDKKind.ArticleCurationSet,
|
||||
content: '',
|
||||
created_at: now(),
|
||||
tags: [
|
||||
@ -465,8 +459,8 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
const handleUnblockNSFW = async () => {
|
||||
const pubkey = userState.user?.pubkey as string
|
||||
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Curationsets],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.ArticleCurationSet],
|
||||
authors: [pubkey],
|
||||
'#d': ['nsfw']
|
||||
}
|
||||
@ -474,12 +468,11 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
setIsLoading(true)
|
||||
setLoadingSpinnerDesc('Finding NSFW list')
|
||||
|
||||
const nsfwListEvent =
|
||||
await RelayController.getInstance().fetchEventFromUserRelays(
|
||||
filter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
const nsfwListEvent = await fetchEventFromUserRelays(
|
||||
filter,
|
||||
pubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
|
||||
if (!nsfwListEvent) {
|
||||
toast.error(`Couldn't get nsfw list event from relays`)
|
||||
@ -490,7 +483,7 @@ const Game = ({ naddr, game, author, aTag }: GameProps) => {
|
||||
|
||||
const unsignedEvent: UnsignedEvent = {
|
||||
pubkey: nsfwListEvent.pubkey,
|
||||
kind: nsfwListEvent.kind,
|
||||
kind: NDKKind.ArticleCurationSet,
|
||||
content: nsfwListEvent.content,
|
||||
created_at: now(),
|
||||
tags: tags.filter((item) => item[0] !== 'a' || item[1] !== aTag)
|
||||
@ -667,6 +660,7 @@ type ReportPopupProps = {
|
||||
}
|
||||
|
||||
const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
||||
const { fetchEventFromUserRelays } = useNDKContext()
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
const [selectedOptions, setSelectedOptions] = useState({
|
||||
actuallyCP: false,
|
||||
@ -720,18 +714,17 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
||||
setLoadingSpinnerDesc(`Finding user's mute list`)
|
||||
// Define the event filter to search for the user's mute list events.
|
||||
// We look for events of a specific kind (Mutelist) authored by the given hexPubkey.
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Mutelist],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.MuteList],
|
||||
authors: [hexPubkey]
|
||||
}
|
||||
|
||||
// Fetch the mute list event from the relays. This returns the event containing the user's mute list.
|
||||
const muteListEvent =
|
||||
await RelayController.getInstance().fetchEventFromUserRelays(
|
||||
filter,
|
||||
hexPubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
const muteListEvent = await fetchEventFromUserRelays(
|
||||
filter,
|
||||
hexPubkey,
|
||||
UserRelaysType.Write
|
||||
)
|
||||
|
||||
let unsignedEvent: UnsignedEvent
|
||||
|
||||
@ -750,7 +743,7 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
||||
|
||||
unsignedEvent = {
|
||||
pubkey: muteListEvent.pubkey,
|
||||
kind: muteListEvent.kind,
|
||||
kind: NDKKind.MuteList,
|
||||
content: muteListEvent.content,
|
||||
created_at: now(),
|
||||
tags: [...tags]
|
||||
@ -758,7 +751,7 @@ const ReportPopup = ({ aTag, handleClose }: ReportPopupProps) => {
|
||||
} else {
|
||||
unsignedEvent = {
|
||||
pubkey: hexPubkey,
|
||||
kind: kinds.Mutelist,
|
||||
kind: NDKKind.MuteList,
|
||||
content: '',
|
||||
created_at: now(),
|
||||
tags: [['a', aTag]]
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
UserRelaysType
|
||||
} from 'controllers'
|
||||
import { formatDate } from 'date-fns'
|
||||
import { useAppSelector, useDidMount, useReactions } from 'hooks'
|
||||
import { useAppSelector, useDidMount, useNDKContext, useReactions } from 'hooks'
|
||||
import { useComments } from 'hooks/useComments'
|
||||
import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||
import React, {
|
||||
@ -322,11 +322,11 @@ const Filter = React.memo(
|
||||
)
|
||||
|
||||
const Comment = (props: CommentEvent) => {
|
||||
const { findMetadata } = useNDKContext()
|
||||
const [profile, setProfile] = useState<UserProfile>()
|
||||
|
||||
useDidMount(async () => {
|
||||
const metadataController = await MetadataController.getInstance()
|
||||
metadataController.findMetadata(props.pubkey).then((res) => {
|
||||
useDidMount(() => {
|
||||
findMetadata(props.pubkey).then((res) => {
|
||||
setProfile(res)
|
||||
})
|
||||
})
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { NDKEvent, NDKUserProfile, profileFromEvent } from '@nostr-dev-kit/ndk'
|
||||
import {
|
||||
NDKEvent,
|
||||
NDKFilter,
|
||||
NDKKind,
|
||||
NDKSubscriptionCacheUsage,
|
||||
NDKUserProfile,
|
||||
profileFromEvent
|
||||
} from '@nostr-dev-kit/ndk'
|
||||
import { ErrorBoundary } from 'components/ErrorBoundary'
|
||||
import { GameCard } from 'components/GameCard'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||
@ -11,16 +18,14 @@ import {
|
||||
MAX_MODS_PER_PAGE,
|
||||
T_TAG_VALUE
|
||||
} from 'constants.ts'
|
||||
import { RelayController } from 'controllers'
|
||||
import {
|
||||
useAppSelector,
|
||||
useFilteredMods,
|
||||
useGames,
|
||||
useMuteLists,
|
||||
useNDKContext,
|
||||
useNSFWList
|
||||
} from 'hooks'
|
||||
import { Filter, kinds } from 'nostr-tools'
|
||||
import { Subscription } from 'nostr-tools/abstract-relay'
|
||||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
@ -30,7 +35,6 @@ import React, {
|
||||
useState
|
||||
} from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import {
|
||||
FilterOptions,
|
||||
ModDetails,
|
||||
@ -267,56 +271,38 @@ const ModsResult = ({
|
||||
muteLists,
|
||||
nsfwList
|
||||
}: ModsResultProps) => {
|
||||
const hasEffectRun = useRef(false)
|
||||
const [isSubscribing, setIsSubscribing] = useState(false)
|
||||
const { ndk } = useNDKContext()
|
||||
const [mods, setMods] = useState<ModDetails[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
|
||||
useEffect(() => {
|
||||
if (hasEffectRun.current) {
|
||||
return
|
||||
}
|
||||
|
||||
hasEffectRun.current = true // Set it so the effect doesn't run again
|
||||
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.ClassifiedListing],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Classified],
|
||||
'#t': [T_TAG_VALUE]
|
||||
}
|
||||
|
||||
setIsSubscribing(true)
|
||||
const subscription = ndk.subscribe(filter, {
|
||||
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
|
||||
closeOnEose: true
|
||||
})
|
||||
|
||||
let subscriptions: Subscription[] = []
|
||||
subscription.on('event', (ndkEvent) => {
|
||||
if (isModDataComplete(ndkEvent)) {
|
||||
const mod = extractModData(ndkEvent)
|
||||
setMods((prev) => {
|
||||
if (prev.find((e) => e.aTag === mod.aTag)) return [...prev]
|
||||
|
||||
RelayController.getInstance()
|
||||
.subscribeForEvents(filter, [], (event) => {
|
||||
if (isModDataComplete(event)) {
|
||||
const mod = extractModData(event)
|
||||
setMods((prev) => [...prev, mod])
|
||||
}
|
||||
})
|
||||
.then((subs) => {
|
||||
subscriptions = subs
|
||||
})
|
||||
.catch((err) => {
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
'An error occurred in subscribing to relays.',
|
||||
err
|
||||
)
|
||||
toast.error(err.message || err)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsSubscribing(false)
|
||||
})
|
||||
return [...prev, mod]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup function to stop all subscriptions
|
||||
return () => {
|
||||
subscriptions.forEach((sub) => sub.close()) // close each subscription
|
||||
subscription.stop()
|
||||
}
|
||||
}, [])
|
||||
}, [ndk])
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1)
|
||||
@ -357,9 +343,6 @@ const ModsResult = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSubscribing && (
|
||||
<LoadingSpinner desc='Subscribing to relays for mods' />
|
||||
)}
|
||||
<div className='IBMSecMain IBMSMListWrapper'>
|
||||
<div className='IBMSMList'>
|
||||
{filteredModList
|
||||
@ -393,6 +376,7 @@ const UsersResult = ({
|
||||
moderationFilter,
|
||||
muteLists
|
||||
}: UsersResultProps) => {
|
||||
const { fetchEvents } = useNDKContext()
|
||||
const [isFetching, setIsFetching] = useState(false)
|
||||
const [profiles, setProfiles] = useState<NDKUserProfile[]>([])
|
||||
|
||||
@ -402,14 +386,13 @@ const UsersResult = ({
|
||||
if (searchTerm === '') {
|
||||
setProfiles([])
|
||||
} else {
|
||||
const filter: Filter = {
|
||||
kinds: [kinds.Metadata],
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Metadata],
|
||||
search: searchTerm
|
||||
}
|
||||
|
||||
setIsFetching(true)
|
||||
RelayController.getInstance()
|
||||
.fetchEvents(filter, ['wss://purplepag.es', 'wss://user.kindpag.es'])
|
||||
fetchEvents(filter, ['wss://purplepag.es', 'wss://user.kindpag.es'])
|
||||
.then((events) => {
|
||||
const results = events.map((event) => {
|
||||
const ndkEvent = new NDKEvent(undefined, event)
|
||||
@ -425,7 +408,7 @@ const UsersResult = ({
|
||||
setIsFetching(false)
|
||||
})
|
||||
}
|
||||
}, [searchTerm])
|
||||
}, [searchTerm, fetchEvents])
|
||||
|
||||
const filteredProfiles = useMemo(() => {
|
||||
let filtered = [...profiles]
|
||||
|
@ -1,21 +1,22 @@
|
||||
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { useState } from 'react'
|
||||
import { useLocation, useParams } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||
import { ModForm } from '../components/ModForm'
|
||||
import { ProfileSection } from '../components/ProfileSection'
|
||||
import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
|
||||
import '../styles/innerPage.css'
|
||||
import '../styles/styles.css'
|
||||
import '../styles/write.css'
|
||||
import { Filter, nip19 } from 'nostr-tools'
|
||||
import { RelayController } from '../controllers'
|
||||
import { extractModData, log, LogType } from '../utils'
|
||||
import { ModDetails } from '../types'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useState } from 'react'
|
||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||
import { useAppSelector, useDidMount } from '../hooks'
|
||||
import { extractModData, log, LogType } from '../utils'
|
||||
|
||||
export const SubmitModPage = () => {
|
||||
const location = useLocation()
|
||||
const { naddr } = useParams()
|
||||
const { fetchEvent } = useNDKContext()
|
||||
const [modData, setModData] = useState<ModDetails>()
|
||||
const [isFetching, setIsFetching] = useState(false)
|
||||
|
||||
@ -30,15 +31,15 @@ export const SubmitModPage = () => {
|
||||
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||
const { identifier, kind, pubkey, relays = [] } = decoded.data
|
||||
|
||||
const filter: Filter = {
|
||||
const filter: NDKFilter = {
|
||||
'#a': [identifier],
|
||||
authors: [pubkey],
|
||||
kinds: [kind]
|
||||
}
|
||||
|
||||
setIsFetching(true)
|
||||
RelayController.getInstance()
|
||||
.fetchEvent(filter, relays)
|
||||
|
||||
fetchEvent(filter, relays)
|
||||
.then((event) => {
|
||||
if (event) {
|
||||
const extracted = extractModData(event)
|
||||
|
Loading…
Reference in New Issue
Block a user