From de7a8a868abae2c99388c86df0640d41eec2121b Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 29 Aug 2024 19:25:58 +0300 Subject: [PATCH] feat(notificiations): initial changes to check notifications per each user --- src/controllers/RelayController.ts | 44 ++++++++------------ src/pages/create/index.tsx | 32 +++++++++----- src/pages/sign/index.tsx | 22 +++++----- src/utils/nostr.ts | 67 +++++++++++------------------- src/utils/relays.ts | 17 ++++++-- src/utils/utils.ts | 12 ++++++ 6 files changed, 102 insertions(+), 92 deletions(-) diff --git a/src/controllers/RelayController.ts b/src/controllers/RelayController.ts index df33b4b..a4552b0 100644 --- a/src/controllers/RelayController.ts +++ b/src/controllers/RelayController.ts @@ -2,7 +2,8 @@ import { Event, Filter, Relay } from 'nostr-tools' import { settleAllFullfilfedPromises, normalizeWebSocketURL, - timeout + publishToRelay, + isPromiseFulfilled } from '../utils' import { SIGIT_RELAY } from '../utils/const' @@ -261,17 +262,17 @@ export class RelayController { event: Event, relayUrls: string[] = [] ): Promise => { - if (!relayUrls.includes(SIGIT_RELAY)) { - /** - * NOTE: To avoid side-effects on external relayUrls array passed as argument - * re-assigned relayUrls with added sigit relay instead of just appending to same array - */ - relayUrls = [...relayUrls, SIGIT_RELAY] // Add app relay to relays array if not exists already - } + /** + * Ensure that the default Sigit Relay is included. + * Copy the array instead of mutating it. + */ + const updatedRelayUrls = !relayUrls.includes(SIGIT_RELAY) + ? [...relayUrls, SIGIT_RELAY] + : [...relayUrls] // connect to all specified relays const relays = await settleAllFullfilfedPromises( - relayUrls, + updatedRelayUrls, this.connectRelay ) @@ -280,26 +281,15 @@ export class RelayController { throw new Error('No relay is connected to publish event!') } - const publishedOnRelays: string[] = [] // List to track which relays successfully published the event - - // Create a promise for publishing the event to each connected relay - const publishPromises = relays.map(async (relay) => { - try { - await Promise.race([ - relay.publish(event), // Publish the event to the relay - timeout(20 * 1000) // Set a timeout to handle cases where publishing takes too long - ]) - publishedOnRelays.push(relay.url) // Add the relay URL to the list of successfully published relays - } catch (err) { - console.error(`Failed to publish event on relay: ${relay.url}`, err) - } - }) - - // Wait for all publish operations to complete (either fulfilled or rejected) - await Promise.allSettled(publishPromises) + const settledPromises: PromiseSettledResult[] = + await Promise.allSettled( + relays.map(async (relay) => publishToRelay(relay, event)) + ) // Return the list of relay URLs where the event was published - return publishedOnRelays + return settledPromises + .filter(isPromiseFulfilled) + .map((res) => res.value) as string[] } } diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 2c32f7d..29bca5e 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -46,7 +46,9 @@ import { shorten, signEventForMetaFile, updateUsersAppData, - uploadToFileStorage + uploadToFileStorage, + isPromiseFulfilled, + isPromiseRejected } from '../../utils' import { Container } from '../../components/Container' import styles from './style.module.scss' @@ -720,7 +722,7 @@ export const CreatePage = () => { : viewers.map((viewer) => viewer.pubkey) ).filter((receiver) => receiver !== usersPubkey) - return receivers.map((receiver) => sendNotification(receiver, meta)) + return receivers.map(async (receiver) => sendNotification(receiver, meta)) } const handleCreate = async () => { @@ -784,15 +786,25 @@ export const CreatePage = () => { if (!event) return setLoadingSpinnerDesc('Sending notifications to counterparties') - const promises = sendNotifications(meta) + const notifications = await Promise.allSettled(sendNotifications(meta)) + if (notifications.every(isPromiseFulfilled)) { + toast.success('finished processing notifications!') + } else { + notifications + .filter(isPromiseRejected) + .map((res: PromiseRejectedResult) => res.reason) + .forEach((res) => console.log('rejected result: ', res)) + } - await Promise.all(promises) - .then(() => { - toast.success('Notifications sent successfully') - }) - .catch(() => { - toast.error('Failed to publish notifications') - }) + // const promises = sendNotifications(meta) + + // await Promise.all(promises) + // .then(() => { + // toast.success('Notifications sent successfully') + // }) + // .catch(() => { + // toast.error('Failed to publish notifications') + // }) navigate(appPrivateRoutes.sign, { state: { meta: meta } }) } else { diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 8720954..12e4c81 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -701,17 +701,19 @@ export const SignPage = () => { setLoadingSpinnerDesc('Sending notifications') const users = Array.from(userSet) - const promises = users.map((user) => - sendNotification(npubToHex(user)!, meta) + console.log('users: ', users) + const publishedRelays = await Promise.all( + users.map(async (user) => sendNotification(npubToHex(user)!, meta)) ) - await Promise.all(promises) - .then(() => { - toast.success('Notifications sent successfully') - setMeta(meta) - }) - .catch(() => { - toast.error('Failed to publish notifications') - }) + console.log('published relays: ', publishedRelays) + // await Promise.all(promises) + // .then(() => { + // toast.success('Notifications sent successfully') + // setMeta(meta) + // }) + // .catch(() => { + // toast.error('Failed to publish notifications') + // }) setIsLoading(false) } diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index eceb8d8..d16467e 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -916,50 +916,33 @@ const processReceivedEvent = async (event: Event, difficulty: number = 5) => { * @param meta - Metadata associated with the notification. */ export const sendNotification = async (receiver: string, meta: Meta) => { - // Retrieve the user's public key from the state - const usersPubkey = (store.getState().auth as AuthState).usersPubkey! + try { + // Retrieve the user's public key from the state + const usersPubkey = (store.getState().auth as AuthState).usersPubkey! + // Create an unsigned event object with the provided metadata + const unsignedEvent: UnsignedEvent = { + kind: 938, + pubkey: usersPubkey, + content: JSON.stringify(meta), + tags: [], + created_at: unixNow() + } - // Create an unsigned event object with the provided metadata - const unsignedEvent: UnsignedEvent = { - kind: 938, - pubkey: usersPubkey, - content: JSON.stringify(meta), - tags: [], - created_at: unixNow() - } + // Wrap the unsigned event with the receiver's information + const wrappedEvent = createWrap(unsignedEvent, receiver) - // Wrap the unsigned event with the receiver's information - const wrappedEvent = createWrap(unsignedEvent, receiver) + // Instantiate the MetadataController to retrieve relay list metadata + const metadataController = new MetadataController() - // Instantiate the MetadataController to retrieve relay list metadata - const metadataController = new MetadataController() - const relaySet = await metadataController - .findRelayListMetadata(receiver) - .catch((err) => { - // Log an error if retrieving relay list metadata fails - console.log( - `An error occurred while finding relay list metadata for ${hexToNpub(receiver)}`, - err - ) - return null + const relaySet = await metadataController.findRelayListMetadata(receiver) + + return await Promise.race([ + relayController.publish(wrappedEvent, relaySet.read), + timeout(40 * 1000) + ]) + } catch (error) { + throw new Error(`Failed to publish a notification for ${receiver}`, { + cause: error }) - - // Return if metadata retrieval failed - if (!relaySet) return - - // Ensure relay list is not empty - if (relaySet.read.length === 0) return - - // Publish the notification event to the recipient's read relays - await Promise.race([ - relayController.publish(wrappedEvent, relaySet.read), - timeout(40 * 1000) - ]).catch((err) => { - // Log an error if publishing the notification event fails - console.log( - `An error occurred while publishing notification event for ${hexToNpub(receiver)}`, - err - ) - throw err - }) + } } diff --git a/src/utils/relays.ts b/src/utils/relays.ts index 5b79f3d..90e2021 100644 --- a/src/utils/relays.ts +++ b/src/utils/relays.ts @@ -1,6 +1,6 @@ -import { Event, Filter, kinds, UnsignedEvent } from 'nostr-tools' +import { Event, Filter, kinds, Relay, UnsignedEvent } from 'nostr-tools' import { RelayList } from 'nostr-tools/kinds' -import { getRelayInfo, unixNow } from '.' +import { getRelayInfo, timeout, unixNow } from '.' import { NostrController, relayController } from '../controllers' import { localCache } from '../services' import { RelayMap, RelaySet } from '../types' @@ -229,6 +229,16 @@ const publishRelayMap = async ( return Promise.reject('Publishing updated relay map was unsuccessful.') } +const publishToRelay = async (relay: Relay, event: Event): Promise => { + try { + await Promise.race([relay.publish(event), timeout(20 * 1000)]) + return relay.url + } catch (err) { + console.error(`Failed to publish event on relay: ${relay.url}`, err) + throw new Error(`Failed to publish event on relay: ${relay.url}`) + } +} + export { findRelayListAndUpdateCache, findRelayListInCache, @@ -238,5 +248,6 @@ export { getUserRelaySet, isOlderThanOneDay, isOlderThanOneWeek, - publishRelayMap + publishRelayMap, + publishToRelay } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f32e14e..807e972 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -118,3 +118,15 @@ export const settleAllFullfilfedPromises = async ( return acc }, []) } + +export const isPromiseFulfilled = ( + result: PromiseSettledResult +): result is PromiseFulfilledResult => { + return result.status === 'fulfilled' +} + +export const isPromiseRejected = ( + result: PromiseSettledResult +): result is PromiseRejectedResult => { + return result.status === 'rejected' +}