Compare commits

..

No commits in common. "4233ad4ce79d7e2cb34d734bf5cb68c576b047b4" and "1ff8d9ed7b81e282b4ecaa6f5303b28538707f6c" have entirely different histories.

9 changed files with 100 additions and 155 deletions

View File

@ -80,18 +80,6 @@ export const ModCard = ({
</svg> </svg>
<p>420</p> <p>420</p>
</div> </div>
<div className='cMMFootReactionsElement'>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-64 0 512 512'
width='1em'
height='1em'
fill='currentColor'
>
<path d='M240.5 224H352C365.3 224 377.3 232.3 381.1 244.7C386.6 257.2 383.1 271.3 373.1 280.1L117.1 504.1C105.8 513.9 89.27 514.7 77.19 505.9C65.1 497.1 60.7 481.1 66.59 467.4L143.5 288H31.1C18.67 288 6.733 279.7 2.044 267.3C-2.645 254.8 .8944 240.7 10.93 231.9L266.9 7.918C278.2-1.92 294.7-2.669 306.8 6.114C318.9 14.9 323.3 30.87 317.4 44.61L240.5 224z'></path>
</svg>
<p>420</p>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -120,7 +120,6 @@ type ZapQRProps = {
handleClose: () => void handleClose: () => void
handleQRExpiry: () => void handleQRExpiry: () => void
setTotalZapAmount?: Dispatch<SetStateAction<number>> setTotalZapAmount?: Dispatch<SetStateAction<number>>
setHasZapped?: Dispatch<SetStateAction<boolean>>
} }
export const ZapQR = React.memo( export const ZapQR = React.memo(
@ -128,8 +127,7 @@ export const ZapQR = React.memo(
paymentRequest, paymentRequest,
handleClose, handleClose,
handleQRExpiry, handleQRExpiry,
setTotalZapAmount, setTotalZapAmount
setHasZapped
}: ZapQRProps) => { }: ZapQRProps) => {
useDidMount(() => { useDidMount(() => {
ZapController.getInstance() ZapController.getInstance()
@ -139,7 +137,6 @@ export const ZapQR = React.memo(
if (setTotalZapAmount) { if (setTotalZapAmount) {
const amount = getZapAmount(zapReceipt) const amount = getZapAmount(zapReceipt)
setTotalZapAmount((prev) => prev + amount) setTotalZapAmount((prev) => prev + amount)
if (setHasZapped) setHasZapped(true)
} }
}) })
.catch((err) => { .catch((err) => {
@ -230,8 +227,6 @@ type ZapPopUpProps = {
notCloseAfterZap?: boolean notCloseAfterZap?: boolean
lastNode?: ReactNode lastNode?: ReactNode
setTotalZapAmount?: Dispatch<SetStateAction<number>> setTotalZapAmount?: Dispatch<SetStateAction<number>>
setHasZapped?: Dispatch<SetStateAction<boolean>>
handleClose: () => void handleClose: () => void
} }
@ -244,7 +239,6 @@ export const ZapPopUp = ({
lastNode, lastNode,
notCloseAfterZap, notCloseAfterZap,
setTotalZapAmount, setTotalZapAmount,
setHasZapped,
handleClose handleClose
}: ZapPopUpProps) => { }: ZapPopUpProps) => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@ -343,8 +337,6 @@ export const ZapPopUp = ({
toast.success(`Successfully sent ${amount} sats!`) toast.success(`Successfully sent ${amount} sats!`)
if (setTotalZapAmount) { if (setTotalZapAmount) {
setTotalZapAmount((prev) => prev + amount) setTotalZapAmount((prev) => prev + amount)
if (setHasZapped) setHasZapped(true)
} }
if (!notCloseAfterZap) { if (!notCloseAfterZap) {
@ -365,8 +357,7 @@ export const ZapPopUp = ({
notCloseAfterZap, notCloseAfterZap,
handleClose, handleClose,
generatePaymentRequest, generatePaymentRequest,
setTotalZapAmount, setTotalZapAmount
setHasZapped
]) ])
const handleQRExpiry = useCallback(() => { const handleQRExpiry = useCallback(() => {
@ -447,7 +438,6 @@ export const ZapPopUp = ({
handleClose={handleQRClose} handleClose={handleQRClose}
handleQRExpiry={handleQRExpiry} handleQRExpiry={handleQRExpiry}
setTotalZapAmount={setTotalZapAmount} setTotalZapAmount={setTotalZapAmount}
setHasZapped={setHasZapped}
/> />
)} )}
{lastNode} {lastNode}

View File

@ -121,10 +121,10 @@ export class MetadataController {
public findUserRelays = async ( public findUserRelays = async (
hexKey: string, hexKey: string,
userRelaysType: UserRelaysType = UserRelaysType.Both userRelaysType: UserRelaysType = UserRelaysType.Both
): Promise<string[]> => { ) => {
log(true, LogType.Info, ` Finding user's relays`, hexKey, userRelaysType) log(true, LogType.Info, ` Finding user's relays`, hexKey, userRelaysType)
const ndkRelayListPromise = getRelayListForUser(hexKey, this.ndk) const ndkRelayListPromise = await getRelayListForUser(hexKey, this.ndk)
// Use Promise.race to either get the NDKRelayList instance or handle the timeout // Use Promise.race to either get the NDKRelayList instance or handle the timeout
return await Promise.race([ return await Promise.race([
@ -132,12 +132,11 @@ export class MetadataController {
timeout() // Custom timeout function that rejects after a specified time timeout() // Custom timeout function that rejects after a specified time
]) ])
.then((ndkRelayList) => { .then((ndkRelayList) => {
if (ndkRelayList) return ndkRelayList[userRelaysType] return ndkRelayList[userRelaysType]
return [] // Return an empty array if ndkRelayList is undefined
}) })
.catch((err) => { .catch((err) => {
log(true, LogType.Error, err) log(true, LogType.Error, err)
return [] // Return an empty array if an error occurs return [] as string[] // Return an empty array if an error occurs
}) })
} }

View File

@ -1,4 +1,4 @@
import { Event, Filter, kinds, nip57, Relay } from 'nostr-tools' import { Event, Filter, kinds, Relay } from 'nostr-tools'
import { import {
extractZapAmount, extractZapAmount,
log, log,
@ -600,8 +600,10 @@ export class RelayController {
const processedEvents: string[] = [] // To keep track of processed events const processedEvents: string[] = [] // To keep track of processed events
// Create a promise for each relay subscription // Create a promise for each relay subscription
const subscriptions = relays.map((relay) => const subPromises = relays.map((relay) => {
relay.subscribe([filter], { return new Promise<void>((resolve) => {
// Subscribe to the relay with the specified filter
const sub = relay.subscribe([filter], {
// Handle incoming events // Handle incoming events
onevent: (e) => { onevent: (e) => {
// Process event only if it hasn't been processed before // Process event only if it hasn't been processed before
@ -609,11 +611,18 @@ export class RelayController {
processedEvents.push(e.id) processedEvents.push(e.id)
eventHandler(e) // Call the event handler with the event eventHandler(e) // Call the event handler with the event
} }
},
// Handle the End-Of-Stream (EOSE) message
oneose: () => {
sub.close() // Close the subscription
resolve() // Resolve the promise when EOSE is received
} }
}) })
) })
})
return subscriptions // Wait for all subscriptions to complete
await Promise.allSettled(subPromises)
} }
getTotalZapAmount = async ( getTotalZapAmount = async (
@ -629,90 +638,53 @@ export class RelayController {
UserRelaysType.Read UserRelaysType.Read
) )
const appRelay = import.meta.env.VITE_APP_RELAY // add app relay to relays array
if (!relayUrls.includes(appRelay)) { relayUrls.push(import.meta.env.VITE_APP_RELAY)
relayUrls.push(appRelay)
} // add admin relays to relays array
metadataController.adminRelays.forEach((url) => {
relayUrls.push(url)
})
// Connect to all specified relays // Connect to all specified relays
const relayPromises = relayUrls.map((relayUrl) => const relayPromises = relayUrls.map((relayUrl) =>
this.connectRelay(relayUrl) this.connectRelay(relayUrl)
) )
await Promise.allSettled(relayPromises)
// 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 accumulatedZapAmount = 0
let hasZapped = false let hasZapped = false
const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates const eventIds = new Set<string>() // To keep track of event IDs and avoid duplicates
const filters: Filter[] = [ const filter: Filter = {
{ kinds: [kinds.Zap]
kinds: [kinds.Zap],
'#e': [eTag]
} }
]
if (aTag) { if (aTag) {
filters.push({ filter['#a'] = [aTag]
kinds: [kinds.Zap], } else {
'#a': [aTag] filter['#e'] = [eTag]
})
} }
// Create a promise for each relay subscription // Create a promise for each relay subscription
const subPromises = relays.map((relay) => { const subPromises = this.connectedRelays.map((relay) => {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
// Subscribe to the relay with the specified filter // Subscribe to the relay with the specified filter
const sub = relay.subscribe(filters, { const sub = relay.subscribe([filter], {
// Handle incoming events // Handle incoming events
onevent: (e) => { onevent: (e) => {
// Add the event to the array if it's not a duplicate // Add the event to the array if it's not a duplicate
if (!eventIds.has(e.id)) { if (!eventIds.has(e.id)) {
eventIds.add(e.id) // Record the event ID eventIds.add(e.id) // Record the event ID
const amount = extractZapAmount(e)
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 accumulatedZapAmount += amount
if (amount > 0) {
if (!hasZapped) { if (!hasZapped) {
hasZapped = zapRequest.pubkey === currentLoggedInUser hasZapped =
} e.tags.findIndex(
(tag) => tag[0] === 'P' && tag[1] === currentLoggedInUser
) > -1
} }
} }
}, },

View File

@ -12,7 +12,6 @@ import {
} from '../types' } from '../types'
import { log, LogType, npubToHex } from '../utils' import { log, LogType, npubToHex } from '../utils'
import { RelayController } from './relay' import { RelayController } from './relay'
import { MetadataController, UserRelaysType } from './metadata'
/** /**
* Singleton class to manage zap related operations. * Singleton class to manage zap related operations.
@ -148,7 +147,7 @@ export class ZapController {
const cleanup = () => { const cleanup = () => {
clearTimeout(timeout) clearTimeout(timeout)
subscriptions.forEach((subscription) => subscription.close()) sub.close()
} }
// Polling timeout // Polling timeout
@ -161,11 +160,13 @@ export class ZapController {
pollingTimeout || 6 * 60 * 1000 // 6 minutes pollingTimeout || 6 * 60 * 1000 // 6 minutes
) )
const relaysTag = zapRequest.tags.find((t) => t[0] === 'relays') const relay = await RelayController.getInstance().connectRelay(
if (!relaysTag) this.appRelay
throw new Error('Zap request does not contain relays tag.') )
const relayUrls = relaysTag.slice(1) if (!relay) {
return reject('Polling Zap Receipt: Could not connect to app relay!')
}
// filter relay for event of kind 9735 // filter relay for event of kind 9735
const filter: Filter = { const filter: Filter = {
@ -173,11 +174,9 @@ export class ZapController {
since: created_at since: created_at
} }
const subscriptions = const sub = relay.subscribe([filter], {
await RelayController.getInstance().subscribeForEvents( // Handle incoming events
filter, onevent: async (event) => {
relayUrls,
async (event) => {
// get description tag of the event // get description tag of the event
const description = event.tags.filter( const description = event.tags.filter(
(tag) => tag[0] === 'description' (tag) => tag[0] === 'description'
@ -193,7 +192,7 @@ export class ZapController {
} }
} }
} }
) })
}) })
} }
@ -281,21 +280,11 @@ export class ZapController {
if (!recipientHexKey) throw 'Invalid recipient pubKey.' if (!recipientHexKey) throw 'Invalid recipient pubKey.'
const metadataController = await MetadataController.getInstance()
const receiverReadRelays = await metadataController.findUserRelays(
recipientHexKey,
UserRelaysType.Read
)
if (!receiverReadRelays.includes(this.appRelay)) {
receiverReadRelays.push(this.appRelay)
}
const zapRequest: ZapRequest = { const zapRequest: ZapRequest = {
kind: kinds.ZapRequest, kind: kinds.ZapRequest,
content, content,
tags: [ tags: [
['relays', ...receiverReadRelays], ['relays', `${this.appRelay}`],
['amount', `${amount}`], ['amount', `${amount}`],
['p', recipientHexKey] ['p', recipientHexKey]
], ],

View File

@ -48,10 +48,7 @@ export const HomePage = () => {
loop loop
> >
{LANDING_PAGE_DATA.featuredSlider.map((naddr) => ( {LANDING_PAGE_DATA.featuredSlider.map((naddr) => (
<SwiperSlide <SwiperSlide className='swiper-slide IBMSMSliderContainerWrapperSlider'>
key={naddr}
className='swiper-slide IBMSMSliderContainerWrapperSlider'
>
<SlideContent naddr={naddr} /> <SlideContent naddr={naddr} />
</SwiperSlide> </SwiperSlide>
))} ))}
@ -68,11 +65,7 @@ export const HomePage = () => {
</div> </div>
<div className='IBMSMList IBMSMListFeaturedAlt'> <div className='IBMSMList IBMSMListFeaturedAlt'>
{LANDING_PAGE_DATA.featuredGames.map((game) => ( {LANDING_PAGE_DATA.featuredGames.map((game) => (
<GameCard <GameCard title={game.title} imageUrl={game.imageUrl} />
key={game.title}
title={game.title}
imageUrl={game.imageUrl}
/>
))} ))}
</div> </div>
<div className='IBMSMAction'> <div className='IBMSMAction'>

View File

@ -13,7 +13,7 @@ import {
Filter as NostrEventFilter, Filter as NostrEventFilter,
UnsignedEvent UnsignedEvent
} from 'nostr-tools' } from 'nostr-tools'
import React, { useEffect, useMemo } from 'react' import React, { useMemo } from 'react'
import { Dispatch, SetStateAction, useState } from 'react' import { Dispatch, SetStateAction, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
@ -58,10 +58,6 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
author: AuthorFilterEnum.All_Comments author: AuthorFilterEnum.All_Comments
}) })
useEffect(() => {
setCommentCount(commentEvents.length)
}, [commentEvents, setCommentCount])
const userState = useAppSelector((state) => state.user) const userState = useAppSelector((state) => state.user)
useDidMount(async () => { useDidMount(async () => {
@ -206,6 +202,8 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => {
return true return true
} }
setCommentCount(commentEvents.length)
const comments = useMemo(() => { const comments = useMemo(() => {
let filteredComments = commentEvents let filteredComments = commentEvents
if (filterOptions.author === AuthorFilterEnum.Creator_Comments) { if (filterOptions.author === AuthorFilterEnum.Creator_Comments) {
@ -590,7 +588,6 @@ const Zap = (props: Event) => {
eventId={props.id} eventId={props.id}
handleClose={() => setIsOpen(false)} handleClose={() => setIsOpen(false)}
setTotalZapAmount={setTotalZappedAmount} setTotalZapAmount={setTotalZappedAmount}
setHasZapped={setHasZapped}
/> />
)} )}
</> </>

View File

@ -74,7 +74,6 @@ export const Zap = ({ modDetails }: ZapProps) => {
lastNode={<ZapSite />} lastNode={<ZapSite />}
notCloseAfterZap notCloseAfterZap
setTotalZapAmount={setTotalZappedAmount} setTotalZapAmount={setTotalZappedAmount}
setHasZapped={setHasZapped}
/> />
)} )}
</> </>

View File

@ -104,13 +104,31 @@ export const npubToHex = (pubKey: string): string | null => {
* @returns The zap amount in the form of a number, converted from the extracted data, or 0 if the amount cannot be determined. * @returns The zap amount in the form of a number, converted from the extracted data, or 0 if the amount cannot be determined.
*/ */
export const extractZapAmount = (event: Event): number => { export const extractZapAmount = (event: Event): number => {
// Find the 'description' tag within the event's tags
const description = event.tags.find(
(tag) => tag[0] === 'description' && typeof tag[1] === 'string'
)
// If the 'description' tag is found and it has a valid value
if (description && description[1]) {
try {
// Parse the description as JSON to get additional details
const parsedDescription: Event = JSON.parse(description[1])
// Find the 'amount' tag within the parsed description's tags // Find the 'amount' tag within the parsed description's tags
const amountTag = event.tags.find( const amountTag = parsedDescription.tags.find(
(tag) => tag[0] === 'amount' && typeof tag[1] === 'string' (tag) => tag[0] === 'amount' && typeof tag[1] === 'string'
) )
// If the 'amount' tag is found and it has a valid value, convert it to an integer and return // If the 'amount' tag is found and it has a valid value, convert it to an integer and return
if (amountTag && amountTag[1]) return parseInt(amountTag[1]) / 1000 if (amountTag && amountTag[1]) return parseInt(amountTag[1]) / 1000
} catch (error) {
// Log an error message if JSON parsing fails
console.log(
`An error occurred while parsing description of zap event: ${error}`
)
}
}
// Return 0 if the zap amount cannot be determined // Return 0 if the zap amount cannot be determined
return 0 return 0