Relay operations refactored with NDK for publishing events (+ more. Wrapped up refactoring), pagination scroll up on click, body scroll disable/enable when popups appear/disappear, nsfw tag shown on mod cards if mod post is nsfw #92
@ -5,8 +5,7 @@ import { handleModImageError } from '../utils'
|
|||||||
import { ModDetails } from 'types'
|
import { ModDetails } from 'types'
|
||||||
import { getModPageRoute } from 'routes'
|
import { getModPageRoute } from 'routes'
|
||||||
import { kinds, nip19 } from 'nostr-tools'
|
import { kinds, nip19 } from 'nostr-tools'
|
||||||
import { useDidMount, useReactions } from 'hooks'
|
import { useDidMount, useNDKContext, useReactions } from 'hooks'
|
||||||
import { RelayController } from 'controllers'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { useComments } from 'hooks/useComments'
|
import { useComments } from 'hooks/useComments'
|
||||||
|
|
||||||
@ -19,10 +18,10 @@ export const ModCard = React.memo((props: ModDetails) => {
|
|||||||
eTag: props.id,
|
eTag: props.id,
|
||||||
aTag: props.aTag
|
aTag: props.aTag
|
||||||
})
|
})
|
||||||
|
const { getTotalZapAmount } = useNDKContext()
|
||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
RelayController.getInstance()
|
getTotalZapAmount(props.author, props.id, props.aTag)
|
||||||
.getTotalZapAmount(props.author, props.id, props.aTag)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setTotalZappedAmount(res.accumulatedZapAmount)
|
setTotalZappedAmount(res.accumulatedZapAmount)
|
||||||
})
|
})
|
||||||
|
@ -5,7 +5,8 @@ import NDK, {
|
|||||||
NDKKind,
|
NDKKind,
|
||||||
NDKRelaySet,
|
NDKRelaySet,
|
||||||
NDKSubscriptionCacheUsage,
|
NDKSubscriptionCacheUsage,
|
||||||
NDKUser
|
NDKUser,
|
||||||
|
zapInvoiceFromEvent
|
||||||
} from '@nostr-dev-kit/ndk'
|
} from '@nostr-dev-kit/ndk'
|
||||||
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
|
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie'
|
||||||
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from 'constants.ts'
|
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from 'constants.ts'
|
||||||
@ -36,16 +37,25 @@ interface NDKContextType {
|
|||||||
fetchEvents: (filter: NDKFilter) => Promise<NDKEvent[]>
|
fetchEvents: (filter: NDKFilter) => Promise<NDKEvent[]>
|
||||||
fetchEvent: (filter: NDKFilter) => Promise<NDKEvent | null>
|
fetchEvent: (filter: NDKFilter) => Promise<NDKEvent | null>
|
||||||
fetchEventsFromUserRelays: (
|
fetchEventsFromUserRelays: (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => Promise<NDKEvent[]>
|
) => Promise<NDKEvent[]>
|
||||||
fetchEventFromUserRelays: (
|
fetchEventFromUserRelays: (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => Promise<NDKEvent | null>
|
) => Promise<NDKEvent | null>
|
||||||
findMetadata: (pubkey: string) => Promise<UserProfile>
|
findMetadata: (pubkey: string) => Promise<UserProfile>
|
||||||
|
getTotalZapAmount: (
|
||||||
|
user: string,
|
||||||
|
eTag: string,
|
||||||
|
aTag?: string,
|
||||||
|
currentLoggedInUser?: string
|
||||||
|
) => Promise<{
|
||||||
|
accumulatedZapAmount: number
|
||||||
|
hasZapped: boolean
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the context with an initial value of `null`
|
// Create the context with an initial value of `null`
|
||||||
@ -218,10 +228,10 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
* @returns A promise that resolves with an array of events.
|
* @returns A promise that resolves with an array of events.
|
||||||
*/
|
*/
|
||||||
const fetchEventsFromUserRelays = async (
|
const fetchEventsFromUserRelays = async (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => {
|
): Promise<NDKEvent[]> => {
|
||||||
// Find the user's relays.
|
// Find the user's relays.
|
||||||
const relayUrls = await getRelayListForUser(hexKey, ndk)
|
const relayUrls = await getRelayListForUser(hexKey, ndk)
|
||||||
.then((ndkRelayList) => {
|
.then((ndkRelayList) => {
|
||||||
@ -266,7 +276,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
* @returns A promise that resolves to the fetched event or null if the operation fails.
|
||||||
*/
|
*/
|
||||||
const fetchEventFromUserRelays = async (
|
const fetchEventFromUserRelays = async (
|
||||||
filter: NDKFilter,
|
filter: NDKFilter | NDKFilter[],
|
||||||
hexKey: string,
|
hexKey: string,
|
||||||
userRelaysType: UserRelaysType
|
userRelaysType: UserRelaysType
|
||||||
) => {
|
) => {
|
||||||
@ -296,6 +306,52 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
return userProfile
|
return userProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTotalZapAmount = async (
|
||||||
|
user: string,
|
||||||
|
eTag: string,
|
||||||
|
aTag?: string,
|
||||||
|
currentLoggedInUser?: string
|
||||||
|
) => {
|
||||||
|
const filters: NDKFilter[] = [
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.Zap],
|
||||||
|
'#e': [eTag],
|
||||||
|
'#p': [user]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (aTag) {
|
||||||
|
filters.push({
|
||||||
|
kinds: [NDKKind.Zap],
|
||||||
|
'#a': [aTag],
|
||||||
|
'#p': [user]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const zapEvents = await fetchEventsFromUserRelays(
|
||||||
|
filters,
|
||||||
|
user,
|
||||||
|
UserRelaysType.Read
|
||||||
|
)
|
||||||
|
|
||||||
|
let accumulatedZapAmount = 0
|
||||||
|
let hasZapped = false
|
||||||
|
|
||||||
|
zapEvents.forEach((zap) => {
|
||||||
|
const zapInvoice = zapInvoiceFromEvent(zap)
|
||||||
|
if (zapInvoice) {
|
||||||
|
accumulatedZapAmount += Math.round(zapInvoice.amount / 1000)
|
||||||
|
|
||||||
|
if (!hasZapped) hasZapped = zapInvoice.zappee === currentLoggedInUser
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
accumulatedZapAmount,
|
||||||
|
hasZapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NDKContext.Provider
|
<NDKContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -305,7 +361,8 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
fetchEvent,
|
fetchEvent,
|
||||||
fetchEventsFromUserRelays,
|
fetchEventsFromUserRelays,
|
||||||
fetchEventFromUserRelays,
|
fetchEventFromUserRelays,
|
||||||
findMetadata
|
findMetadata,
|
||||||
|
getTotalZapAmount
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import { Event, Filter, kinds, nip57, Relay } from 'nostr-tools'
|
import { Event, Relay } from 'nostr-tools'
|
||||||
import {
|
import { log, LogType, normalizeWebSocketURL, timeout } from '../utils'
|
||||||
extractZapAmount,
|
|
||||||
log,
|
|
||||||
LogType,
|
|
||||||
normalizeWebSocketURL,
|
|
||||||
timeout
|
|
||||||
} from '../utils'
|
|
||||||
import { MetadataController, UserRelaysType } from './metadata'
|
import { MetadataController, UserRelaysType } from './metadata'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -371,122 +365,4 @@ export class RelayController {
|
|||||||
// Return the list of relay URLs where the event was successfully published
|
// Return the list of relay URLs where the event was successfully published
|
||||||
return publishedOnRelays
|
return publishedOnRelays
|
||||||
}
|
}
|
||||||
|
|
||||||
getTotalZapAmount = async (
|
|
||||||
user: string,
|
|
||||||
eTag: string,
|
|
||||||
aTag?: string,
|
|
||||||
currentLoggedInUser?: string
|
|
||||||
) => {
|
|
||||||
const metadataController = await MetadataController.getInstance()
|
|
||||||
|
|
||||||
const relayUrls = await metadataController.findUserRelays(
|
|
||||||
user,
|
|
||||||
UserRelaysType.Read
|
|
||||||
)
|
|
||||||
|
|
||||||
const appRelay = import.meta.env.VITE_APP_RELAY
|
|
||||||
if (!relayUrls.includes(appRelay)) {
|
|
||||||
relayUrls.push(appRelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to all specified relays
|
|
||||||
const relayPromises = relayUrls.map((relayUrl) =>
|
|
||||||
this.connectRelay(relayUrl)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Use Promise.allSettled to wait for all promises to settle
|
|
||||||
const results = await Promise.allSettled(relayPromises)
|
|
||||||
|
|
||||||
// Extract non-null values from fulfilled promises in a single pass
|
|
||||||
const relays = results.reduce<Relay[]>((acc, result) => {
|
|
||||||
if (result.status === 'fulfilled') {
|
|
||||||
const value = result.value
|
|
||||||
if (value) {
|
|
||||||
acc.push(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
let accumulatedZapAmount = 0
|
|
||||||
let hasZapped = false
|
|
||||||
|
|
||||||
const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates
|
|
||||||
|
|
||||||
const filters: Filter[] = [
|
|
||||||
{
|
|
||||||
kinds: [kinds.Zap],
|
|
||||||
'#e': [eTag]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
if (aTag) {
|
|
||||||
filters.push({
|
|
||||||
kinds: [kinds.Zap],
|
|
||||||
'#a': [aTag]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a promise for each relay subscription
|
|
||||||
const subPromises = relays.map((relay) => {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
// Subscribe to the relay with the specified filter
|
|
||||||
const sub = relay.subscribe(filters, {
|
|
||||||
// Handle incoming events
|
|
||||||
onevent: (e) => {
|
|
||||||
// Add the event to the array if it's not a duplicate
|
|
||||||
if (!eventIds.has(e.id)) {
|
|
||||||
eventIds.add(e.id) // Record the event ID
|
|
||||||
|
|
||||||
const zapRequestStr = e.tags.find(
|
|
||||||
(t) => t[0] === 'description'
|
|
||||||
)?.[1]
|
|
||||||
if (!zapRequestStr) return
|
|
||||||
|
|
||||||
const error = nip57.validateZapRequest(zapRequestStr)
|
|
||||||
if (error) return
|
|
||||||
|
|
||||||
let zapRequest: Event | null = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
zapRequest = JSON.parse(zapRequestStr)
|
|
||||||
} catch (error) {
|
|
||||||
log(
|
|
||||||
true,
|
|
||||||
LogType.Error,
|
|
||||||
'Error occurred in parsing zap request',
|
|
||||||
error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!zapRequest) return
|
|
||||||
|
|
||||||
const amount = extractZapAmount(zapRequest)
|
|
||||||
accumulatedZapAmount += amount
|
|
||||||
|
|
||||||
if (amount > 0) {
|
|
||||||
if (!hasZapped) {
|
|
||||||
hasZapped = zapRequest.pubkey === currentLoggedInUser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Handle the End-Of-Stream (EOSE) message
|
|
||||||
oneose: () => {
|
|
||||||
sub.close() // Close the subscription
|
|
||||||
resolve() // Resolve the promise when EOSE is received
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for all subscriptions to complete
|
|
||||||
await Promise.allSettled(subPromises)
|
|
||||||
|
|
||||||
return {
|
|
||||||
accumulatedZapAmount,
|
|
||||||
hasZapped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,23 +9,5 @@ export const useNDKContext = () => {
|
|||||||
'NDKContext should not be used in out component tree hierarchy'
|
'NDKContext should not be used in out component tree hierarchy'
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
return { ...ndkContext }
|
||||||
ndk,
|
|
||||||
fetchEvents,
|
|
||||||
fetchEvent,
|
|
||||||
fetchEventsFromUserRelays,
|
|
||||||
fetchEventFromUserRelays,
|
|
||||||
fetchMods,
|
|
||||||
findMetadata
|
|
||||||
} = ndkContext
|
|
||||||
|
|
||||||
return {
|
|
||||||
ndk,
|
|
||||||
fetchEvents,
|
|
||||||
fetchEvent,
|
|
||||||
fetchEventsFromUserRelays,
|
|
||||||
fetchEventFromUserRelays,
|
|
||||||
fetchMods,
|
|
||||||
findMetadata
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -496,15 +496,14 @@ const Reactions = (props: Event) => {
|
|||||||
|
|
||||||
const Zap = (props: Event) => {
|
const Zap = (props: Event) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
||||||
const [hasZapped, setHasZapped] = useState(false)
|
const [hasZapped, setHasZapped] = useState(false)
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
const { getTotalZapAmount } = useNDKContext()
|
||||||
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
|
||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
RelayController.getInstance()
|
getTotalZapAmount(
|
||||||
.getTotalZapAmount(
|
|
||||||
props.pubkey,
|
props.pubkey,
|
||||||
props.id,
|
props.id,
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ZapSplit } from 'components/Zap'
|
import { ZapSplit } from 'components/Zap'
|
||||||
import { RelayController } from 'controllers'
|
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
|
||||||
import { useAppSelector, useDidMount } from 'hooks'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { ModDetails } from 'types'
|
import { ModDetails } from 'types'
|
||||||
@ -12,15 +11,14 @@ type ZapProps = {
|
|||||||
|
|
||||||
export const Zap = ({ modDetails }: ZapProps) => {
|
export const Zap = ({ modDetails }: ZapProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
||||||
const [hasZapped, setHasZapped] = useState(false)
|
const [hasZapped, setHasZapped] = useState(false)
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
const { getTotalZapAmount } = useNDKContext()
|
||||||
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
|
||||||
|
|
||||||
useDidMount(() => {
|
useDidMount(() => {
|
||||||
RelayController.getInstance()
|
getTotalZapAmount(
|
||||||
.getTotalZapAmount(
|
|
||||||
modDetails.author,
|
modDetails.author,
|
||||||
modDetails.id,
|
modDetails.id,
|
||||||
modDetails.aTag,
|
modDetails.aTag,
|
||||||
|
Loading…
Reference in New Issue
Block a user