From b7d1fea077ae45534271663bd9efa1108f85ad6b Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 15 Jan 2025 10:38:40 +0100 Subject: [PATCH] fix: addressing comments --- .../MarkTypeStrategy/Signature/index.tsx | 9 +- src/components/PDFView/PdfMarking.tsx | 9 +- src/components/PDFView/index.tsx | 5 + src/hooks/useAuth.ts | 7 +- src/pages/settings/servers/index.tsx | 23 +- src/pages/sign/index.tsx | 1 - src/pages/verify/index.tsx | 16 +- src/store/rootReducer.ts | 4 +- src/store/servers/reducer.ts | 6 +- src/store/servers/types.ts | 2 +- src/utils/file-servers.ts | 64 +++--- src/utils/nostr.ts | 213 ++---------------- 12 files changed, 95 insertions(+), 264 deletions(-) diff --git a/src/components/MarkTypeStrategy/Signature/index.tsx b/src/components/MarkTypeStrategy/Signature/index.tsx index 57b19e6..90a441f 100644 --- a/src/components/MarkTypeStrategy/Signature/index.tsx +++ b/src/components/MarkTypeStrategy/Signature/index.tsx @@ -13,6 +13,13 @@ import { MarkRenderSignature } from './Render' export const SignatureStrategy: MarkStrategy = { input: MarkInputSignature, render: MarkRenderSignature, + /** + * Encrypts a stringified signature object, creates an encrypted JSON file, + * and uploads it to a file storage if the user is online. + * @param value + * @param encryptionKey + * @returns the original value string + */ encryptAndUpload: async (value, encryptionKey) => { // Value is the stringified signature object // Encode it to the arrayBuffer @@ -43,8 +50,6 @@ export const SignatureStrategy: MarkStrategy = { console.info( `${file.name} uploaded to following file storages: ${urls.join(', ')}` ) - // This bit was returning an url, and return of this function is being set to mark.value, so it kind of - // does not make sense to return an url to the file storage return value } catch (error) { if (error instanceof Error) { diff --git a/src/components/PDFView/PdfMarking.tsx b/src/components/PDFView/PdfMarking.tsx index 71dfdd9..0f16db2 100644 --- a/src/components/PDFView/PdfMarking.tsx +++ b/src/components/PDFView/PdfMarking.tsx @@ -26,6 +26,11 @@ import styles from '../UsersDetails.tsx/style.module.scss' interface PdfMarkingProps { currentUserMarks: CurrentUserMark[] files: CurrentUserFile[] + /** + * Currently, loading spinner is present if `files` array is of length 0, + * Which means if no files are found, loading spinner will be spinning indefinitely + * For that reason `noFiles` is introduced to set the loading off when fetching is finished. + */ noFiles?: boolean handleExport: () => void handleEncryptedExport: () => void @@ -179,12 +184,10 @@ const PdfMarking = (props: PdfMarkingProps) => { currentUserMarks={currentUserMarks} otherUserMarks={otherUserMarks} /> - {noFiles ? ( + {noFiles && ( We were not able to retrieve the files. - ) : ( - '' )} diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 8fcaaeb..d8679ac 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -10,6 +10,11 @@ interface PdfViewProps { currentFile: CurrentUserFile | null currentUserMarks: CurrentUserMark[] files: CurrentUserFile[] + /** + * Currently, loading spinner is present if `files` array is of length 0, + * Which means if no files are found, loading spinner will be spinning indefinitely + * For that reason `noFiles` is introduced to set the loading off when fetching is finished. + */ noFiles?: boolean handleMarkClick: (id: number) => void otherUserMarks: Mark[] diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 39e5c5d..e2ef3ec 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -95,12 +95,9 @@ export const useAuth = () => { }) ) - const ndkRelayListPromise = getNDKRelayList(pubkey) - const serverMapPromise = getFileServerMap(pubkey, fetchEvent) - const [ndkRelayList, serverMap] = await Promise.all([ - ndkRelayListPromise, - serverMapPromise + getNDKRelayList(pubkey), + getFileServerMap(pubkey, fetchEvent) ]) const relays = ndkRelayList.relays diff --git a/src/pages/settings/servers/index.tsx b/src/pages/settings/servers/index.tsx index addfea5..79336b8 100644 --- a/src/pages/settings/servers/index.tsx +++ b/src/pages/settings/servers/index.tsx @@ -50,15 +50,15 @@ export const ServersPage = () => { const fetchFileServers = async () => { if (usersPubkey) { - await getFileServerMap(usersPubkey, fetchEvent).then((res) => { - if (res.map) { - if (Object.keys(res.map).length === 0) { - serverRequirementWarning() - } + const servers = await getFileServerMap(usersPubkey, fetchEvent) - setBlossomServersMap(res.map) + if (servers.map) { + if (Object.keys(servers.map).length === 0) { + serverRequirementWarning() } - }) + + setBlossomServersMap(servers.map) + } } else { noUserKeyWarning() } @@ -97,7 +97,7 @@ export const ServersPage = () => { if (Object.keys(blossomServersMap).includes(serverURL)) return toast.warning('This server is already added.') - const valid = await validateFileServer(serverURL).catch(() => null) + const valid = await validateFileServer(serverURL) if (!valid) return toast.warning( `Server URL ${serverURL} does not seem to be a valid file server.` @@ -160,18 +160,19 @@ export const ServersPage = () => { * @param serverURL */ const validateFileServer = (serverURL: string) => { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { axios .get(serverURL) .then((res) => { if (res && res.data?.toLowerCase().includes('blossom server')) { resolve(true) } else { - reject(false) + resolve(false) } }) .catch((err) => { - reject(err) + console.error('Error validating file server.', err) + resolve(false) }) }) } diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index d1bf242..d620b10 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -169,7 +169,6 @@ export const SignPage = () => { createSignatureContent.markConfig, usersPubkey! ) - // TODO figure out why markConfig does not contain the usersPubkey when multiple signer const signedMarks = extractMarksFromSignedMeta(meta) const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks) const otherUserMarks = findOtherUserMarks(signedMarks, usersPubkey!) diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 2e16103..3c2afe1 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -63,6 +63,11 @@ import { MarkRender } from '../../components/MarkTypeStrategy/MarkRender.tsx' interface PdfViewProps { files: CurrentUserFile[] + /** + * Currently, loading spinner is present if `files` array is of length 0, + * Which means if no files are found, loading spinner will be spinning indefinitely + * For that reason `noFiles` is introduced to set the loading off when fetching is finished. + */ noFiles?: boolean currentFile: CurrentUserFile | null parsedSignatureEvents: { @@ -460,7 +465,9 @@ export const VerifyPage = () => { if (!zip) { if (!isLastZipUrl) continue // Skip to next zipUrl - break // If last zipUrl break out of loop + // If it's the last zip url, and still no `zip` found, break out of loop, + // it means no files were successfully fetched or all files failed the validation (hash check). + break } const files: { [fileName: string]: SigitFile } = {} @@ -481,10 +488,9 @@ export const VerifyPage = () => { entryArrayBuffer, entryFileName ) - const hash = await getHash(entryArrayBuffer) - if (hash) { - fileHashes[entryFileName.replace(/^files\//, '')] = hash - } + + fileHashes[entryFileName.replace(/^files\//, '')] = + await getHash(entryArrayBuffer) } else { fileHashes[entryFileName.replace(/^files\//, '')] = null } diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 2de29a0..8daf1c8 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -9,14 +9,14 @@ import { RelaysDispatchTypes, RelaysState } from './relays/types' import UserAppDataReducer from './userAppData/reducer' import { UserAppDataDispatchTypes } from './userAppData/types' import { UserDispatchTypes, UserState } from './user/types' -import { ServersDispatchTypes, ServersState } from './servers/types.ts' +import { ServersDispatchTypes, FileServersState } from './servers/types.ts' import serversReducer from './servers/reducer' export interface State { auth: AuthState user: UserState relays: RelaysState - servers: ServersState + servers: FileServersState userAppData?: UserAppData } diff --git a/src/store/servers/reducer.ts b/src/store/servers/reducer.ts index 1f06a44..dc67acf 100644 --- a/src/store/servers/reducer.ts +++ b/src/store/servers/reducer.ts @@ -1,7 +1,7 @@ import * as ActionTypes from '../actionTypes' -import { ServersDispatchTypes, ServersState } from './types' +import { ServersDispatchTypes, FileServersState } from './types' -const initialState: ServersState = { +const initialState: FileServersState = { map: undefined, mapUpdated: undefined } @@ -9,7 +9,7 @@ const initialState: ServersState = { const reducer = ( state = initialState, action: ServersDispatchTypes -): ServersState => { +): FileServersState => { switch (action.type) { case ActionTypes.SET_SERVER_MAP: return { ...state, map: action.payload, mapUpdated: Date.now() } diff --git a/src/store/servers/types.ts b/src/store/servers/types.ts index 41bda0a..be0baca 100644 --- a/src/store/servers/types.ts +++ b/src/store/servers/types.ts @@ -2,7 +2,7 @@ import * as ActionTypes from '../actionTypes' import { RestoreState } from '../actions' import { ServerMap } from '../../types' -export type ServersState = { +export type FileServersState = { map?: ServerMap mapUpdated?: number } diff --git a/src/utils/file-servers.ts b/src/utils/file-servers.ts index 108e14d..60c6af6 100644 --- a/src/utils/file-servers.ts +++ b/src/utils/file-servers.ts @@ -21,39 +21,41 @@ const getFileServerMap = async ( opts?: NDKSubscriptionOptions | undefined ) => Promise ): Promise<{ map: FileServerMap; mapUpdated?: number }> => { - // More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/96.md - const eventFilter: Filter = { - kinds: [kinds.FileServerPreference], - authors: [npub] - } + try { + // More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/96.md + const eventFilter: Filter = { + kinds: [kinds.FileServerPreference], + authors: [npub] + } - const event = await fetchEvent(eventFilter).catch((err) => { + const event = await fetchEvent(eventFilter) + + if (event) { + // Handle found event 10096 + const fileServersMap: FileServerMap = {} + + const serverTags = event.tags.filter((tag) => tag[0] === 'server') + + serverTags.forEach((tag) => { + const url = tag[1] + const serverType = tag[2] + + // if 3rd element of server tag is undefined, server is WRITE and READ + fileServersMap[url] = { + write: serverType ? serverType === 'write' : true, + read: serverType ? serverType === 'read' : true + } + }) + + return Promise.resolve({ + map: fileServersMap, + mapUpdated: event.created_at + }) + } else { + return Promise.resolve({ map: getDefaultFileServerMap() }) + } + } catch (err) { return Promise.reject(err) - }) - - if (event) { - // Handle found event 10096 - const fileServersMap: FileServerMap = {} - - const serverTags = event.tags.filter((tag) => tag[0] === 'server') - - serverTags.forEach((tag) => { - const url = tag[1] - const serverType = tag[2] - - // if 3rd element of server tag is undefined, server is WRITE and READ - fileServersMap[url] = { - write: serverType ? serverType === 'write' : true, - read: serverType ? serverType === 'read' : true - } - }) - - return Promise.resolve({ - map: fileServersMap, - mapUpdated: event.created_at - }) - } else { - return Promise.resolve({ map: getDefaultFileServerMap() }) } } diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index 1915fe7..948d2f3 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -1,6 +1,6 @@ import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk' import { hexToBytes } from '@noble/hashes/utils' -import axios, { AxiosResponse } from 'axios' +import axios from 'axios' import { truncate } from 'lodash' import { Event, @@ -411,24 +411,20 @@ export const uploadUserAppDataToBlossom = async ( // Finalize the event with the private key const authEvent = finalizeEvent(event, hexToBytes(privateKey)) - const uploadPromises: Promise>[] = [] - // Upload the file to the file storage services using Axios - for (const preferredServer of preferredServers) { - const uploadPromise = axios.put( - `${preferredServer}/upload`, - file, - { - headers: { - Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)) // Set authorization header + const responses = await Promise.all( + preferredServers.map((preferredServer) => { + return axios.put( + `${preferredServer}/upload`, + file, + { + headers: { + Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)) // Set authorization header + } } - } - ) - - uploadPromises.push(uploadPromise) - } - - const responses = await Promise.all(uploadPromises) + ) + }) + ) // Return the URLs of the uploaded files return responses.map((response) => response.data.url) as string[] @@ -520,189 +516,6 @@ export const getUserAppDataFromBlossom = async ( return parsedContent } -// /** -// * Function to subscribe to sigits notifications for a specified public key. -// * @param pubkey - The public key to subscribe to. -// * @returns A promise that resolves when the subscription is successful. -// */ -// export const subscribeForSigits = async (pubkey: string) => { -// // Instantiate the MetadataController to retrieve relay list metadata -// const metadataController = MetadataController.getInstance() -// const relaySet = await metadataController -// .findRelayListMetadata(pubkey) -// .catch((err) => { -// // Log an error if retrieving relay list metadata fails -// console.log( -// `An error occurred while finding relay list metadata for ${hexToNpub(pubkey)}`, -// err -// ) -// return null -// }) -// -// // Return if metadata retrieval failed -// if (!relaySet) return -// -// // Ensure relay list is not empty -// if (relaySet.read.length === 0) return -// -// // Define the filter for the subscription -// const filter: Filter = { -// kinds: [1059], -// '#p': [pubkey] -// } -// -// // Process the received event synchronously -// const events = await relayController.fetchEvents(filter, relaySet.read) -// for (const e of events) { -// await processReceivedEvent(e) -// } -// -// // Async processing of the events has a race condition -// // relayController.subscribeForEvents(filter, relaySet.read, (event) => { -// // processReceivedEvent(event) -// // }) -// } - -// const processReceivedEvent = async (event: Event, difficulty: number = 5) => { -// const processedEvents = store.getState().userAppData?.processedGiftWraps -// -// // Abort processing if userAppData is undefined -// if (!processedEvents) return -// -// if (processedEvents.includes(event.id)) return -// -// store.dispatch(updateProcessedGiftWraps([...processedEvents, event.id])) -// -// // validate PoW -// // Count the number of leading zero bits in the hash -// const leadingZeroes = countLeadingZeroes(event.id) -// if (leadingZeroes < difficulty) return -// -// // decrypt the content of gift wrap event -// const nostrController = NostrController.getInstance() -// const decrypted = await nostrController.nip44Decrypt( -// event.pubkey, -// event.content -// ) -// -// const internalUnsignedEvent = await parseJson(decrypted).catch( -// (err) => { -// console.log( -// 'An error occurred in parsing the internal unsigned event', -// err -// ) -// return null -// } -// ) -// -// if (!internalUnsignedEvent || internalUnsignedEvent.kind !== 938) return -// -// const parsedContent = await parseJson( -// 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 -// let encryptionKey: string | undefined -// if (!notification.keys) return -// -// const { sender, keys } = notification.keys -// -// // Retrieve the user's public key from the state -// const usersPubkey = store.getState().auth.usersPubkey! -// 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.metaUrls, -// encryptionKey -// ) -// } catch (error) { -// console.error(`An error occured fetching meta file from storage`, error) -// return -// } -// } else { -// meta = parsedContent -// } -// -// await updateUsersAppData(meta) -// } - -// /** -// * Function to send a notification to a specified receiver. -// * @param receiver - The recipient's public key. -// * @param notification - Url pointing to metadata associated with the notification on blossom and keys to decrypt. -// */ -// export const sendNotification = async ( -// receiver: string, -// notification: SigitNotification -// ) => { -// // Retrieve the user's public key from the state -// const usersPubkey = store.getState().auth.usersPubkey! -// -// // Create an unsigned event object with the provided metadata -// const unsignedEvent: UnsignedEvent = { -// kind: 938, -// pubkey: usersPubkey, -// content: JSON.stringify(notification), -// tags: [], -// created_at: unixNow() -// } -// -// // Wrap the unsigned event with the receiver's information -// const wrappedEvent = createWrap(unsignedEvent, receiver) -// -// // Instantiate the MetadataController to retrieve relay list metadata -// const metadataController = MetadataController.getInstance() -// 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 -// }) -// -// // 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 -// }) -// } - /** * Show user's name, first available in order: display_name, name, or npub as fallback * @param npub User identifier, it can be either pubkey or npub1 (we only show npub)