chore: process received events all together instead of one by one which casuses in-consistencies due to async nature of redux updates
All checks were successful
Open PR on Staging / audit_and_check (pull_request) Successful in 44s

This commit is contained in:
daniyal 2025-01-04 11:24:35 +05:00
parent c4c0ecba4a
commit 3a09d4c595
6 changed files with 184 additions and 153 deletions

View File

@ -27,17 +27,25 @@ import {
export interface NDKContextType { export interface NDKContextType {
ndk: NDK ndk: NDK
fetchEvents: (filter: NDKFilter) => Promise<NDKEvent[]> fetchEvents: (
fetchEvent: (filter: NDKFilter) => Promise<NDKEvent | null> filter: NDKFilter,
opts?: NDKSubscriptionOptions
) => Promise<NDKEvent[]>
fetchEvent: (
filter: NDKFilter,
opts?: NDKSubscriptionOptions
) => Promise<NDKEvent | null>
fetchEventsFromUserRelays: ( fetchEventsFromUserRelays: (
filter: NDKFilter | NDKFilter[], filter: NDKFilter | NDKFilter[],
hexKey: string, hexKey: string,
userRelaysType: UserRelaysType userRelaysType: UserRelaysType,
opts?: NDKSubscriptionOptions
) => Promise<NDKEvent[]> ) => Promise<NDKEvent[]>
fetchEventFromUserRelays: ( fetchEventFromUserRelays: (
filter: NDKFilter | NDKFilter[], filter: NDKFilter | NDKFilter[],
hexKey: string, hexKey: string,
userRelaysType: UserRelaysType userRelaysType: UserRelaysType,
opts?: NDKSubscriptionOptions
) => Promise<NDKEvent | null> ) => Promise<NDKEvent | null>
findMetadata: ( findMetadata: (
pubkey: string, pubkey: string,
@ -67,8 +75,8 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
}, []) }, [])
const ndk = useMemo(() => { const ndk = useMemo(() => {
localStorage.setItem('debug', '*') // localStorage.setItem('debug', '*')
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'degmod-db' }) const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'sigit-db' })
dexieAdapter.locking = true dexieAdapter.locking = true
const ndk = new NDK({ const ndk = new NDK({
enableOutboxModel: true, enableOutboxModel: true,
@ -88,11 +96,15 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
* @param filter - The filter criteria to find the event. * @param filter - The filter criteria to find the event.
* @returns Returns a promise that resolves to the found event or null if not found. * @returns Returns a promise that resolves to the found event or null if not found.
*/ */
const fetchEvents = async (filter: NDKFilter): Promise<NDKEvent[]> => { const fetchEvents = async (
filter: NDKFilter,
opts?: NDKSubscriptionOptions
): Promise<NDKEvent[]> => {
return ndk return ndk
.fetchEvents(filter, { .fetchEvents(filter, {
closeOnEose: true, closeOnEose: true,
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
...opts
}) })
.then((ndkEventSet) => { .then((ndkEventSet) => {
const ndkEvents = Array.from(ndkEventSet) const ndkEvents = Array.from(ndkEventSet)
@ -112,8 +124,11 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
* @param filter - The filter criteria to find the event. * @param filter - The filter criteria to find the event.
* @returns Returns a promise that resolves to the found event or null if not found. * @returns Returns a promise that resolves to the found event or null if not found.
*/ */
const fetchEvent = async (filter: NDKFilter) => { const fetchEvent = async (
const events = await fetchEvents(filter) filter: NDKFilter,
opts?: NDKSubscriptionOptions
) => {
const events = await fetchEvents(filter, opts)
if (events.length === 0) return null if (events.length === 0) return null
return events[0] return events[0]
} }
@ -130,7 +145,8 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
const fetchEventsFromUserRelays = async ( const fetchEventsFromUserRelays = async (
filter: NDKFilter | NDKFilter[], filter: NDKFilter | NDKFilter[],
hexKey: string, hexKey: string,
userRelaysType: UserRelaysType userRelaysType: UserRelaysType,
opts?: NDKSubscriptionOptions
): Promise<NDKEvent[]> => { ): Promise<NDKEvent[]> => {
// Find the user's relays (10s timeout). // Find the user's relays (10s timeout).
const relayUrls = await Promise.race([ const relayUrls = await Promise.race([
@ -156,7 +172,11 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
return ndk return ndk
.fetchEvents( .fetchEvents(
filter, filter,
{ closeOnEose: true, cacheUsage: NDKSubscriptionCacheUsage.PARALLEL }, {
closeOnEose: true,
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
...opts
},
relayUrls.length relayUrls.length
? NDKRelaySet.fromRelayUrls(relayUrls, ndk, true) ? NDKRelaySet.fromRelayUrls(relayUrls, ndk, true)
: undefined : undefined
@ -185,12 +205,14 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
const fetchEventFromUserRelays = async ( const fetchEventFromUserRelays = async (
filter: NDKFilter | NDKFilter[], filter: NDKFilter | NDKFilter[],
hexKey: string, hexKey: string,
userRelaysType: UserRelaysType userRelaysType: UserRelaysType,
opts?: NDKSubscriptionOptions
) => { ) => {
const events = await fetchEventsFromUserRelays( const events = await fetchEventsFromUserRelays(
filter, filter,
hexKey, hexKey,
userRelaysType userRelaysType,
opts
) )
if (events.length === 0) return null if (events.length === 0) return null
return events[0] return events[0]

View File

@ -2,7 +2,13 @@ import { useCallback } from 'react'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { bytesToHex } from '@noble/hashes/utils' import { bytesToHex } from '@noble/hashes/utils'
import { NDKEvent, NDKFilter, NDKKind, NDKRelaySet } from '@nostr-dev-kit/ndk' import {
NDKEvent,
NDKFilter,
NDKKind,
NDKRelaySet,
NDKSubscriptionCacheUsage
} from '@nostr-dev-kit/ndk'
import _ from 'lodash' import _ from 'lodash'
import { import {
Event, Event,
@ -74,7 +80,9 @@ export const useNDK = () => {
'#d': [dTag] '#d': [dTag]
} }
const encryptedContent = await fetchEvent(filter) const encryptedContent = await fetchEvent(filter, {
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY
})
.then((event) => { .then((event) => {
if (event) return event.content if (event) return event.content
@ -171,30 +179,31 @@ export const useNDK = () => {
}, [usersPubkey, fetchEvent]) }, [usersPubkey, fetchEvent])
const updateUsersAppData = useCallback( const updateUsersAppData = useCallback(
async (meta: Meta) => { async (metaArray: Meta[]) => {
if (!appData || !appData.keyPair || !usersPubkey) return null if (!appData || !appData.keyPair || !usersPubkey) return null
const sigits = _.cloneDeep(appData.sigits) const sigits = _.cloneDeep(appData.sigits)
let isUpdated = false
for (const meta of metaArray) {
const createSignatureEvent = await parseJson<Event>( const createSignatureEvent = await parseJson<Event>(
meta.createSignature meta.createSignature
).catch((err) => { ).catch((err) => {
console.log('err in parsing the createSignature event:>> ', err) console.log('Error in parsing the createSignature event:', err)
toast.error( toast.error(
err.message || 'error occurred in parsing the create signature event' err.message ||
'Error occurred in parsing the create signature event'
) )
return null return null
}) })
if (!createSignatureEvent) return null if (!createSignatureEvent) continue
const id = createSignatureEvent.id const id = createSignatureEvent.id
let isUpdated = false
// check if sigit already exists // Check if sigit already exists
if (id in sigits) { if (id in sigits) {
// update meta only if incoming meta is more recent // Update meta only if incoming meta is more recent
// than already existing one
const existingMeta = sigits[id] const existingMeta = sigits[id]
if (existingMeta.modifiedAt < meta.modifiedAt) { if (existingMeta.modifiedAt < meta.modifiedAt) {
sigits[id] = meta sigits[id] = meta
@ -204,6 +213,7 @@ export const useNDK = () => {
sigits[id] = meta sigits[id] = meta
isUpdated = true isUpdated = true
} }
}
if (!isUpdated) return null if (!isUpdated) return null
@ -215,34 +225,31 @@ export const useNDK = () => {
appData.keyPair.private appData.keyPair.private
).catch((err) => { ).catch((err) => {
console.log( console.log(
'An error occurred in uploading user app data file to blossom server', 'Error uploading user app data file to Blossom server:',
err err
) )
toast.error( toast.error(
'An error occurred in uploading user app data file to blossom server' 'Error occurred in uploading user app data file to Blossom server'
) )
return null return null
}) })
if (!newBlossomUrl) return null if (!newBlossomUrl) return null
// insert new blossom url at the start of the array // Insert new blossom URL at the start of the array
blossomUrls.unshift(newBlossomUrl) blossomUrls.unshift(newBlossomUrl)
// only keep last 10 blossom urls, delete older ones // Keep only the last 10 Blossom URLs, delete older ones
if (blossomUrls.length > 10) { if (blossomUrls.length > 10) {
const filesToDelete = blossomUrls.splice(10) const filesToDelete = blossomUrls.splice(10)
filesToDelete.forEach((url) => { filesToDelete.forEach((url) => {
deleteBlossomFile(url, appData.keyPair!.private).catch((err) => { deleteBlossomFile(url, appData.keyPair!.private).catch((err) => {
console.log( console.log('Error removing old file from Blossom server:', err)
'An error occurred in removing old file of user app data from blossom server',
err
)
}) })
}) })
} }
// encrypt content for storing in kind 30078 event // Encrypt content for storing in kind 30078 event
const nostrController = NostrController.getInstance() const nostrController = NostrController.getInstance()
const encryptedContent = await nostrController const encryptedContent = await nostrController
.nip04Encrypt( .nip04Encrypt(
@ -253,20 +260,14 @@ export const useNDK = () => {
}) })
) )
.catch((err) => { .catch((err) => {
console.log( console.log('Error encrypting content for app data:', err)
'An error occurred in encryption of content for app data', toast.error(err.message || 'Error encrypting content for app data')
err
)
toast.error(
err.message ||
'An error occurred in encryption of content for app data'
)
return null return null
}) })
if (!encryptedContent) return null if (!encryptedContent) return null
// generate the identifier for user's appData event // Generate the identifier for user's appData event
const dTag = await getDTagForUserAppData() const dTag = await getDTagForUserAppData()
if (!dTag) return null if (!dTag) return null
@ -281,8 +282,8 @@ export const useNDK = () => {
const signedEvent = await nostrController const signedEvent = await nostrController
.signEvent(updatedEvent) .signEvent(updatedEvent)
.catch((err) => { .catch((err) => {
console.log('An error occurred in signing event', err) console.log('Error signing event:', err)
toast.error(err.message || 'An error occurred in signing event') toast.error(err.message || 'Error signing event')
return null return null
}) })
@ -291,16 +292,14 @@ export const useNDK = () => {
const ndkEvent = new NDKEvent(ndk, signedEvent) const ndkEvent = new NDKEvent(ndk, signedEvent)
const publishResult = await publish(ndkEvent) const publishResult = await publish(ndkEvent)
if (publishResult.length === 0) { if (publishResult.length === 0 || !publishResult) {
toast.error( toast.error('Unexpected error occurred in publishing updated app data')
'An unexpected error occurred in publishing updated app data '
)
return null return null
} }
if (!publishResult) return null console.count('updateUserAppData useNDK')
// update redux store // Update Redux store
dispatch( dispatch(
updateUserAppDataAction({ updateUserAppDataAction({
sigits, sigits,
@ -317,26 +316,31 @@ export const useNDK = () => {
[appData, dispatch, ndk, publish, usersPubkey] [appData, dispatch, ndk, publish, usersPubkey]
) )
const processReceivedEvent = useCallback( const processReceivedEvents = useCallback(
async (event: NDKEvent, difficulty: number = 5) => { async (events: NDKEvent[], difficulty: number = 5) => {
// Abort processing if userAppData is undefined
if (!processedEvents) return if (!processedEvents) return
if (processedEvents.includes(event.id)) return const validMetaArray: Meta[] = [] // Array to store valid Meta objects
const updatedProcessedEvents = [...processedEvents] // Keep track of processed event IDs
dispatch(updateProcessedGiftWraps([...processedEvents, event.id])) for (const event of events) {
// Skip already processed events
if (processedEvents.includes(event.id)) continue
// validate PoW // Validate PoW
// Count the number of leading zero bits in the hash
const leadingZeroes = countLeadingZeroes(event.id) const leadingZeroes = countLeadingZeroes(event.id)
if (leadingZeroes < difficulty) return if (leadingZeroes < difficulty) continue
// decrypt the content of gift wrap event // Decrypt the content of the gift wrap event
const nostrController = NostrController.getInstance() const nostrController = NostrController.getInstance()
const decrypted = await nostrController.nip44Decrypt( const decrypted = await nostrController
event.pubkey, .nip44Decrypt(event.pubkey, event.content)
event.content .catch((err) => {
) console.log('An error occurred in decrypting event content', err)
return null
})
if (!decrypted) continue
const internalUnsignedEvent = await parseJson<UnsignedEvent>( const internalUnsignedEvent = await parseJson<UnsignedEvent>(
decrypted decrypted
@ -348,46 +352,40 @@ export const useNDK = () => {
return null return null
}) })
if (!internalUnsignedEvent || internalUnsignedEvent.kind !== 938) return if (!internalUnsignedEvent || internalUnsignedEvent.kind !== 938)
continue
const parsedContent = await parseJson<Meta | SigitNotification>( const parsedContent = await parseJson<Meta | SigitNotification>(
internalUnsignedEvent.content internalUnsignedEvent.content
).catch((err) => { ).catch((err) => {
console.log( console.log('An error occurred in parsing event content', err)
'An error occurred in parsing the internal unsigned event',
err
)
return null return null
}) })
if (!parsedContent) return if (!parsedContent) continue
let meta: Meta let meta: Meta
if (isSigitNotification(parsedContent)) { if (isSigitNotification(parsedContent)) {
const notification = parsedContent const notification = parsedContent
if (!notification.keys || !usersPubkey) return if (!notification.keys || !usersPubkey) continue
let encryptionKey: string | undefined let encryptionKey: string | undefined
const { sender, keys } = notification.keys const { sender, keys } = notification.keys
// Retrieve the user's public key from the state
const usersNpub = hexToNpub(usersPubkey) const usersNpub = hexToNpub(usersPubkey)
// Check if the user's public key is in the keys object
if (usersNpub in keys) { if (usersNpub in keys) {
// Instantiate the NostrController to decrypt the encryption key encryptionKey = await nostrController
const nostrController = NostrController.getInstance()
const decrypted = await nostrController
.nip04Decrypt(sender, keys[usersNpub]) .nip04Decrypt(sender, keys[usersNpub])
.catch((err) => { .catch((err) => {
console.log('An error occurred in decrypting encryption key', err) console.log(
'An error occurred in decrypting encryption key',
err
)
return undefined return undefined
}) })
encryptionKey = decrypted
} }
try { try {
meta = await fetchMetaFromFileStorage( meta = await fetchMetaFromFileStorage(
notification.metaUrl, notification.metaUrl,
@ -395,16 +393,26 @@ export const useNDK = () => {
) )
} catch (error) { } catch (error) {
console.error( console.error(
`An error occured fetching meta file from storage`, 'An error occurred fetching meta file from storage',
error error
) )
return continue
} }
} else { } else {
meta = parsedContent meta = parsedContent
} }
await updateUsersAppData(meta) validMetaArray.push(meta) // Add valid Meta to the array
updatedProcessedEvents.push(event.id) // Mark event as processed
}
// Update processed events in the Redux store
dispatch(updateProcessedGiftWraps(updatedProcessedEvents))
// Pass the array of Meta objects to updateUsersAppData
if (validMetaArray.length > 0) {
await updateUsersAppData(validMetaArray)
}
}, },
[dispatch, processedEvents, updateUsersAppData, usersPubkey] [dispatch, processedEvents, updateUsersAppData, usersPubkey]
) )
@ -421,14 +429,15 @@ export const useNDK = () => {
const events = await fetchEventsFromUserRelays( const events = await fetchEventsFromUserRelays(
filter, filter,
pubkey, pubkey,
UserRelaysType.Read UserRelaysType.Read,
{
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY
}
) )
for (const e of events) { await processReceivedEvents(events)
await processReceivedEvent(e)
}
}, },
[fetchEventsFromUserRelays, processReceivedEvent] [fetchEventsFromUserRelays, processReceivedEvents]
) )
/** /**

View File

@ -904,7 +904,7 @@ export const CreatePage = () => {
setLoadingSpinnerDesc('Updating user app data') setLoadingSpinnerDesc('Updating user app data')
const event = await updateUsersAppData(meta) const event = await updateUsersAppData([meta])
if (!event) return if (!event) return
const metaUrl = await uploadMetaToFileStorage(meta, encryptionKey) const metaUrl = await uploadMetaToFileStorage(meta, encryptionKey)

View File

@ -58,7 +58,7 @@ export const HomePage = () => {
const usersAppData = useAppSelector((state) => state.userAppData) const usersAppData = useAppSelector((state) => state.userAppData)
useEffect(() => { useEffect(() => {
if (usersAppData) { if (usersAppData?.sigits) {
const getSigitInfo = async () => { const getSigitInfo = async () => {
const parsedSigits: { [key: string]: SigitCardDisplayInfo } = {} const parsedSigits: { [key: string]: SigitCardDisplayInfo } = {}
for (const key in usersAppData.sigits) { for (const key in usersAppData.sigits) {
@ -80,7 +80,7 @@ export const HomePage = () => {
setSigits(usersAppData.sigits) setSigits(usersAppData.sigits)
getSigitInfo() getSigitInfo()
} }
}, [usersAppData]) }, [usersAppData?.sigits])
const onDrop = useCallback( const onDrop = useCallback(
async (acceptedFiles: File[]) => { async (acceptedFiles: File[]) => {

View File

@ -646,7 +646,7 @@ export const SignPage = () => {
encryptionKey: string | undefined encryptionKey: string | undefined
) => { ) => {
setLoadingSpinnerDesc('Updating users app data') setLoadingSpinnerDesc('Updating users app data')
const updatedEvent = await updateUsersAppData(meta) const updatedEvent = await updateUsersAppData([meta])
if (!updatedEvent) { if (!updatedEvent) {
setIsLoading(false) setIsLoading(false)
return return

View File

@ -353,7 +353,7 @@ export const VerifyPage = () => {
updatedMeta.timestamps = [...finalTimestamps] updatedMeta.timestamps = [...finalTimestamps]
updatedMeta.modifiedAt = unixNow() updatedMeta.modifiedAt = unixNow()
const updatedEvent = await updateUsersAppData(updatedMeta) const updatedEvent = await updateUsersAppData([updatedMeta])
if (!updatedEvent) return if (!updatedEvent) return
const metaUrl = await uploadMetaToFileStorage( const metaUrl = await uploadMetaToFileStorage(