From 96bf84a0c49b87ac420fe60f8d86439c4dfbc08c Mon Sep 17 00:00:00 2001 From: freakoverse Date: Wed, 11 Sep 2024 15:14:31 +0000 Subject: [PATCH 1/6] added zap icon / numbers to mod box --- src/components/ModCard.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx index 781d65c..fe9f678 100644 --- a/src/components/ModCard.tsx +++ b/src/components/ModCard.tsx @@ -80,6 +80,18 @@ export const ModCard = ({

420

+
+ + + +

420

+
From 990460d7cfb57207be5caba1f22ad7858d6a0017 Mon Sep 17 00:00:00 2001 From: daniyal Date: Tue, 10 Sep 2024 22:38:14 +0500 Subject: [PATCH 2/6] chore: add keys to list elements in home page --- src/pages/home.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 243f58f..944ba23 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -48,7 +48,10 @@ export const HomePage = () => { loop > {LANDING_PAGE_DATA.featuredSlider.map((naddr) => ( - + ))} @@ -65,7 +68,11 @@ export const HomePage = () => {
{LANDING_PAGE_DATA.featuredGames.map((game) => ( - + ))}
From d15c5a21d9224155824fb149450d632fc9e1eebe Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 11 Sep 2024 16:47:55 +0500 Subject: [PATCH 3/6] chore: fix react render error --- src/pages/mod/internal/comment/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/mod/internal/comment/index.tsx b/src/pages/mod/internal/comment/index.tsx index 2e9fc71..86a1451 100644 --- a/src/pages/mod/internal/comment/index.tsx +++ b/src/pages/mod/internal/comment/index.tsx @@ -13,7 +13,7 @@ import { Filter as NostrEventFilter, UnsignedEvent } from 'nostr-tools' -import React, { useMemo } from 'react' +import React, { useEffect, useMemo } from 'react' import { Dispatch, SetStateAction, useState } from 'react' import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' @@ -58,6 +58,10 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => { author: AuthorFilterEnum.All_Comments }) + useEffect(() => { + setCommentCount(commentEvents.length) + }, [commentEvents, setCommentCount]) + const userState = useAppSelector((state) => state.user) useDidMount(async () => { @@ -202,8 +206,6 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => { return true } - setCommentCount(commentEvents.length) - const comments = useMemo(() => { let filteredComments = commentEvents if (filterOptions.author === AuthorFilterEnum.Creator_Comments) { From 7a1d0bbfb03e608ada6d427d2538c9c11f1712f5 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 11 Sep 2024 22:25:03 +0500 Subject: [PATCH 4/6] fix: immediately setHasZapped to true after zapping --- src/components/Zap.tsx | 14 ++++++++++++-- src/pages/mod/internal/comment/index.tsx | 1 + src/pages/mod/internal/zap/index.tsx | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/Zap.tsx b/src/components/Zap.tsx index 4da9d26..b78cbe7 100644 --- a/src/components/Zap.tsx +++ b/src/components/Zap.tsx @@ -120,6 +120,7 @@ type ZapQRProps = { handleClose: () => void handleQRExpiry: () => void setTotalZapAmount?: Dispatch> + setHasZapped?: Dispatch> } export const ZapQR = React.memo( @@ -127,7 +128,8 @@ export const ZapQR = React.memo( paymentRequest, handleClose, handleQRExpiry, - setTotalZapAmount + setTotalZapAmount, + setHasZapped }: ZapQRProps) => { useDidMount(() => { ZapController.getInstance() @@ -137,6 +139,7 @@ export const ZapQR = React.memo( if (setTotalZapAmount) { const amount = getZapAmount(zapReceipt) setTotalZapAmount((prev) => prev + amount) + if (setHasZapped) setHasZapped(true) } }) .catch((err) => { @@ -227,6 +230,8 @@ type ZapPopUpProps = { notCloseAfterZap?: boolean lastNode?: ReactNode setTotalZapAmount?: Dispatch> + setHasZapped?: Dispatch> + handleClose: () => void } @@ -239,6 +244,7 @@ export const ZapPopUp = ({ lastNode, notCloseAfterZap, setTotalZapAmount, + setHasZapped, handleClose }: ZapPopUpProps) => { const [isLoading, setIsLoading] = useState(false) @@ -337,6 +343,8 @@ export const ZapPopUp = ({ toast.success(`Successfully sent ${amount} sats!`) if (setTotalZapAmount) { setTotalZapAmount((prev) => prev + amount) + + if (setHasZapped) setHasZapped(true) } if (!notCloseAfterZap) { @@ -357,7 +365,8 @@ export const ZapPopUp = ({ notCloseAfterZap, handleClose, generatePaymentRequest, - setTotalZapAmount + setTotalZapAmount, + setHasZapped ]) const handleQRExpiry = useCallback(() => { @@ -438,6 +447,7 @@ export const ZapPopUp = ({ handleClose={handleQRClose} handleQRExpiry={handleQRExpiry} setTotalZapAmount={setTotalZapAmount} + setHasZapped={setHasZapped} /> )} {lastNode} diff --git a/src/pages/mod/internal/comment/index.tsx b/src/pages/mod/internal/comment/index.tsx index 86a1451..93ea146 100644 --- a/src/pages/mod/internal/comment/index.tsx +++ b/src/pages/mod/internal/comment/index.tsx @@ -590,6 +590,7 @@ const Zap = (props: Event) => { eventId={props.id} handleClose={() => setIsOpen(false)} setTotalZapAmount={setTotalZappedAmount} + setHasZapped={setHasZapped} /> )} diff --git a/src/pages/mod/internal/zap/index.tsx b/src/pages/mod/internal/zap/index.tsx index a2ba08b..ba6d8e9 100644 --- a/src/pages/mod/internal/zap/index.tsx +++ b/src/pages/mod/internal/zap/index.tsx @@ -74,6 +74,7 @@ export const Zap = ({ modDetails }: ZapProps) => { lastNode={} notCloseAfterZap setTotalZapAmount={setTotalZappedAmount} + setHasZapped={setHasZapped} /> )} From 53d47fcb80437a5af1917cf0a1948d7209a6edd3 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 11 Sep 2024 22:25:55 +0500 Subject: [PATCH 5/6] fix: improve findUserRelays method in metadata controller --- src/controllers/metadata.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/controllers/metadata.ts b/src/controllers/metadata.ts index cd2592c..1eb2dad 100644 --- a/src/controllers/metadata.ts +++ b/src/controllers/metadata.ts @@ -121,10 +121,10 @@ export class MetadataController { public findUserRelays = async ( hexKey: string, userRelaysType: UserRelaysType = UserRelaysType.Both - ) => { + ): Promise => { log(true, LogType.Info, `ℹ Finding user's relays`, hexKey, userRelaysType) - const ndkRelayListPromise = await getRelayListForUser(hexKey, this.ndk) + const ndkRelayListPromise = getRelayListForUser(hexKey, this.ndk) // Use Promise.race to either get the NDKRelayList instance or handle the timeout return await Promise.race([ @@ -132,11 +132,12 @@ export class MetadataController { timeout() // Custom timeout function that rejects after a specified time ]) .then((ndkRelayList) => { - return ndkRelayList[userRelaysType] + if (ndkRelayList) return ndkRelayList[userRelaysType] + return [] // Return an empty array if ndkRelayList is undefined }) .catch((err) => { log(true, LogType.Error, err) - return [] as string[] // Return an empty array if an error occurs + return [] // Return an empty array if an error occurs }) } From 34b096b121c6b254ace44ac155ce51539e373518 Mon Sep 17 00:00:00 2001 From: daniyal Date: Wed, 11 Sep 2024 22:27:37 +0500 Subject: [PATCH 6/6] chore(refactor): improve subscription process --- src/controllers/relay.ts | 114 ++++++++++++++++++++++++--------------- src/controllers/zap.ts | 57 ++++++++++++-------- src/utils/nostr.ts | 28 ++-------- 3 files changed, 110 insertions(+), 89 deletions(-) diff --git a/src/controllers/relay.ts b/src/controllers/relay.ts index deae1b8..0b1413f 100644 --- a/src/controllers/relay.ts +++ b/src/controllers/relay.ts @@ -1,4 +1,4 @@ -import { Event, Filter, kinds, Relay } from 'nostr-tools' +import { Event, Filter, kinds, nip57, Relay } from 'nostr-tools' import { extractZapAmount, log, @@ -600,29 +600,20 @@ export class RelayController { const processedEvents: string[] = [] // To keep track of processed events // Create a promise for each relay subscription - const subPromises = relays.map((relay) => { - return new Promise((resolve) => { - // Subscribe to the relay with the specified filter - const sub = relay.subscribe([filter], { - // Handle incoming events - onevent: (e) => { - // Process event only if it hasn't been processed before - if (!processedEvents.includes(e.id)) { - processedEvents.push(e.id) - eventHandler(e) // Call the event handler with the event - } - }, - // Handle the End-Of-Stream (EOSE) message - oneose: () => { - sub.close() // Close the subscription - resolve() // Resolve the promise when EOSE is received + const subscriptions = relays.map((relay) => + relay.subscribe([filter], { + // Handle incoming events + onevent: (e) => { + // Process event only if it hasn't been processed before + if (!processedEvents.includes(e.id)) { + processedEvents.push(e.id) + eventHandler(e) // Call the event handler with the event } - }) + } }) - }) + ) - // Wait for all subscriptions to complete - await Promise.allSettled(subPromises) + return subscriptions } getTotalZapAmount = async ( @@ -638,53 +629,90 @@ export class RelayController { UserRelaysType.Read ) - // add app relay to relays array - relayUrls.push(import.meta.env.VITE_APP_RELAY) - - // add admin relays to relays array - metadataController.adminRelays.forEach((url) => { - relayUrls.push(url) - }) + 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) ) - 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((acc, result) => { + if (result.status === 'fulfilled') { + const value = result.value + if (value) { + acc.push(value) + } + } + return acc + }, []) let accumulatedZapAmount = 0 let hasZapped = false const eventIds = new Set() // To keep track of event IDs and avoid duplicates - const filter: Filter = { - kinds: [kinds.Zap] - } + const filters: Filter[] = [ + { + kinds: [kinds.Zap], + '#e': [eTag] + } + ] if (aTag) { - filter['#a'] = [aTag] - } else { - filter['#e'] = [eTag] + filters.push({ + kinds: [kinds.Zap], + '#a': [aTag] + }) } // Create a promise for each relay subscription - const subPromises = this.connectedRelays.map((relay) => { + const subPromises = relays.map((relay) => { return new Promise((resolve) => { // Subscribe to the relay with the specified filter - const sub = relay.subscribe([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 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 - if (!hasZapped) { - hasZapped = - e.tags.findIndex( - (tag) => tag[0] === 'P' && tag[1] === currentLoggedInUser - ) > -1 + if (amount > 0) { + if (!hasZapped) { + hasZapped = zapRequest.pubkey === currentLoggedInUser + } } } }, diff --git a/src/controllers/zap.ts b/src/controllers/zap.ts index 608fee1..5bbaf51 100644 --- a/src/controllers/zap.ts +++ b/src/controllers/zap.ts @@ -12,6 +12,7 @@ import { } from '../types' import { log, LogType, npubToHex } from '../utils' import { RelayController } from './relay' +import { MetadataController, UserRelaysType } from './metadata' /** * Singleton class to manage zap related operations. @@ -147,7 +148,7 @@ export class ZapController { const cleanup = () => { clearTimeout(timeout) - sub.close() + subscriptions.forEach((subscription) => subscription.close()) } // Polling timeout @@ -160,13 +161,11 @@ export class ZapController { pollingTimeout || 6 * 60 * 1000 // 6 minutes ) - const relay = await RelayController.getInstance().connectRelay( - this.appRelay - ) + const relaysTag = zapRequest.tags.find((t) => t[0] === 'relays') + if (!relaysTag) + throw new Error('Zap request does not contain relays tag.') - if (!relay) { - return reject('Polling Zap Receipt: Could not connect to app relay!') - } + const relayUrls = relaysTag.slice(1) // filter relay for event of kind 9735 const filter: Filter = { @@ -174,25 +173,27 @@ export class ZapController { since: created_at } - const sub = relay.subscribe([filter], { - // Handle incoming events - onevent: async (event) => { - // get description tag of the event - const description = event.tags.filter( - (tag) => tag[0] === 'description' - )[0] + 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] - // 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() + // 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() - resolve(event as ZapReceipt) + resolve(event as ZapReceipt) + } } } - } - }) + ) }) } @@ -280,11 +281,21 @@ export class ZapController { if (!recipientHexKey) throw 'Invalid recipient pubKey.' + const metadataController = await MetadataController.getInstance() + const receiverReadRelays = await metadataController.findUserRelays( + recipientHexKey, + UserRelaysType.Read + ) + + if (!receiverReadRelays.includes(this.appRelay)) { + receiverReadRelays.push(this.appRelay) + } + const zapRequest: ZapRequest = { kind: kinds.ZapRequest, content, tags: [ - ['relays', `${this.appRelay}`], + ['relays', ...receiverReadRelays], ['amount', `${amount}`], ['p', recipientHexKey] ], diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index ef03678..234c69d 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -104,31 +104,13 @@ 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. */ 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' + // Find the 'amount' tag within the parsed description's tags + const amountTag = event.tags.find( + (tag) => tag[0] === 'amount' && 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 - const amountTag = parsedDescription.tags.find( - (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 (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}` - ) - } - } + // 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 // Return 0 if the zap amount cannot be determined return 0