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

View File

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

View File

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

View File

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

View File

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

View File

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