From 5d6a3580a6b3c97afc7a1f015458fb0a51101f52 Mon Sep 17 00:00:00 2001 From: Davinci Date: Thu, 9 May 2024 15:47:46 +0200 Subject: [PATCH 1/9] fix: when decrypting file, have better error messages --- src/pages/decrypt/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/decrypt/index.tsx b/src/pages/decrypt/index.tsx index 6a25cab..7d954dd 100644 --- a/src/pages/decrypt/index.tsx +++ b/src/pages/decrypt/index.tsx @@ -62,7 +62,15 @@ export const DecryptZip = () => { encryptionKey ).catch((err) => { console.log('err in decryption:>> ', err) - toast.error(err.message || 'An error occurred while decrypting file.') + + if (err.message.toLowerCase().includes('expected')) { + toast.error(`The Key seems to be invalid length or format`) + } else if (err.message.includes('The JWK "alg" member was inconsistent')) { + toast.error(`The Key seems to be invalid.`) + } else { + toast.error(err.message || 'An error occurred while decrypting file.') + } + setIsLoading(false) return null }) From 2c2eeba83f2fb6cdf73228733448228151f1de0f Mon Sep 17 00:00:00 2001 From: Davinci Date: Mon, 13 May 2024 07:50:32 +0200 Subject: [PATCH 2/9] fix: increased timeout for extension user prompt --- src/utils/misc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 4ec79e2..c54ce18 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -88,7 +88,7 @@ export const sendDM = async ( const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error('Timeout occurred')) - }, 15000) // Timeout duration = 15 seconds + }, 60000) // Timeout duration = 60 seconds }) // Encrypt the DM content, with timeout From 041bd0daff4ad06b5ef54798f4c43642c05a2d25 Mon Sep 17 00:00:00 2001 From: Davinci Date: Mon, 13 May 2024 08:00:44 +0200 Subject: [PATCH 3/9] feat(profile): picture upload, robohash, website, npub cash --- package-lock.json | 13 ++++ package.json | 2 + src/controllers/ApiController.ts | 80 ++++++++++++++++++++++++ src/controllers/NostrController.ts | 35 +++++++++++ src/pages/profile/index.tsx | 99 +++++++++++++++++++++++++++++- src/types/media.ts | 19 ++++++ src/types/profile.ts | 1 + 7 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 src/controllers/ApiController.ts create mode 100644 src/types/media.ts diff --git a/package-lock.json b/package-lock.json index 7bc19cd..f1556fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@reduxjs/toolkit": "2.2.1", "axios": "1.6.7", "crypto-hash": "3.0.0", + "crypto-js": "^4.2.0", "file-saver": "2.0.5", "jszip": "3.10.1", "lodash": "4.17.21", @@ -31,6 +32,7 @@ "tseep": "1.2.1" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/file-saver": "2.0.7", "@types/lodash": "4.14.202", "@types/react": "^18.2.56", @@ -1938,6 +1940,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -2630,6 +2638,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", diff --git a/package.json b/package.json index fb823c4..20655c0 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@reduxjs/toolkit": "2.2.1", "axios": "1.6.7", "crypto-hash": "3.0.0", + "crypto-js": "^4.2.0", "file-saver": "2.0.5", "jszip": "3.10.1", "lodash": "4.17.21", @@ -34,6 +35,7 @@ "tseep": "1.2.1" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/file-saver": "2.0.7", "@types/lodash": "4.14.202", "@types/react": "^18.2.56", diff --git a/src/controllers/ApiController.ts b/src/controllers/ApiController.ts new file mode 100644 index 0000000..83fd0c7 --- /dev/null +++ b/src/controllers/ApiController.ts @@ -0,0 +1,80 @@ +import axios, { AxiosError, AxiosInstance } from "axios" +import { PostMediaResponse } from "../types/media" +import sha256 from 'crypto-js/sha256' +import Hex from 'crypto-js/enc-hex' +import { NostrController } from "." + +export class ApiController { + private mediaUrl: string = 'https://media.nquiz.io' + private api: AxiosInstance + private baseUrl: string = '' + + private nostrController = NostrController.getInstance() + + constructor(baseUrl?: string) { + this.baseUrl = baseUrl ?? this.baseUrl + + this.api = axios.create({ + withCredentials: true, + baseURL: this.baseUrl, + headers: { + 'Content-Type': 'application/json' + } + }) + } + + uploadMediaImage = async ( + file: Buffer, + ): Promise => { + const mediaUrl = this.mediaUrl + + const formData = new FormData() + const imageBlob = new Blob([file]) + formData.append('mediafile', imageBlob) + + const endpointUrl = 'v1/media' + const tags = [ + ['u', `${mediaUrl}/${endpointUrl}`.replace('https', 'http')], //nostrcheck api, gets http protocol because it is inside a docker container in localhost + ['method', 'POST'], + ['payload', this.hashString('{}')] + ] + + const authToken = await this.nostrController.createNostrHttpAuthToken(tags) + + console.info('Auth token created for media server.', authToken) + + return axios + .post(`${mediaUrl}/api/v1/media`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: `Nostr ${authToken}` + } + }) + .then((res) => { + console.info( + `POST ${mediaUrl}/api/v1/media | res.data:`, + res.data + ) + // Create correct api url, because res.url is not including 'api/v1/' + if (!res || !res.data) { + console.warn('Image upload: no data returned after upload') + } + + const data = res.data + data.directLink = `${data.url.replace(mediaUrl, `${mediaUrl}/api/v1`)}` + + return data + }) + .catch((err: AxiosError) => { + console.info(`POST ${mediaUrl}/api/v1/media | err:`, err) + throw { + code: 500, + message: err.response?.data || err.response || err.message || err + } + }) + } + + hashString = (payload: any): string => { + return sha256(payload).toString(Hex) + } +} \ No newline at end of file diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index a2b0f1e..5016ac8 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -359,4 +359,39 @@ export class NostrController extends EventEmitter { generateDelegatedKey = (): string => { return NDKPrivateKeySigner.generate().privateKey! } + + /** + * Creates Nostr HTTP Auth token + * @param npub npub in hex format + * @param nostrTags tags to be included in the authevent (auth token) + */ +createNostrHttpAuthToken = async ( + nostrTags: string[][] = [] +): Promise => { + const createdAt = Math.round(Date.now() / 1000) + + let authEvent = new NDKEvent(undefined) + authEvent.kind = 27235 + authEvent.tags = nostrTags + authEvent.content = `sigit-${createdAt}` + authEvent.created_at = createdAt + + await this.signEvent(authEvent.rawEvent() as UnsignedEvent) + + console.info('Signed auth event') + + const base64Encoded = this.base64EncodeSignedEvent(authEvent.rawEvent()) + + return base64Encoded +} + +base64EncodeSignedEvent = (event: NostrEvent) => { + try { + const authEventSerialized = JSON.stringify(event) + const token = btoa(authEventSerialized) + return token + } catch (error) { + throw new Error('An error occurred in JSON.stringy of signedAuthEvent') + } +} } diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index e3cb803..bd64e06 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -1,5 +1,5 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy' -import { List, ListItem, ListSubheader, TextField } from '@mui/material' +import { CircularProgress, List, ListItem, ListSubheader, TextField } from '@mui/material' import { UnsignedEvent, nip19, kinds, VerifiedEvent } from 'nostr-tools' import { useEffect, useMemo, useState } from 'react' import { useParams } from 'react-router-dom' @@ -15,6 +15,8 @@ import { Dispatch } from '../../store/store' import { setMetadataEvent } from '../../store/actions' import { LoadingSpinner } from '../../components/LoadingSpinner' import { LoginMethods } from '../../store/auth/types' +import { ApiController } from '../../controllers/ApiController' +import { PostMediaResponse } from '../../types/media' export const ProfilePage = () => { const { npub } = useParams() @@ -27,9 +29,11 @@ export const ProfilePage = () => { const [pubkey, setPubkey] = useState() const [profileMetadata, setProfileMetadata] = useState() const [savingProfileMetadata, setSavingProfileMetadata] = useState(false) + const [avatarLoading, setAvatarLoading] = useState(false) const metadataState = useSelector((state: State) => state.metadata) const keys = useSelector((state: State) => state.auth?.keyPair) const { usersPubkey, loginMethod } = useSelector((state: State) => state.auth) + const [uploadingImage, setUploadingImage] = useState() const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false) @@ -55,6 +59,10 @@ export const ProfilePage = () => { metadataState as VerifiedEvent ) if (metadataContent) { + if (!metadataContent.lud16 || metadataContent.lud16.length < 1) { + metadataContent.lud16 = getLud16() + } + setProfileMetadata(metadataContent) setIsLoading(false) } @@ -74,7 +82,13 @@ export const ProfilePage = () => { if (metadataEvent) { const metadataContent = metadataController.extractProfileMetadataContent(metadataEvent) - if (metadataContent) setProfileMetadata(metadataContent) + if (metadataContent) { + if (!metadataContent.lud16 || metadataContent.lud16.length < 1) { + metadataContent.lud16 = getLud16() + } + + setProfileMetadata(metadataContent) + } } setIsLoading(false) @@ -84,6 +98,16 @@ export const ProfilePage = () => { } }, [isUsersOwnProfile, metadataState, pubkey, metadataController]) + const getLud16 = () => { + let lud16 = '' + + if (npub) { + if (npub) lud16 = `${npub}@npub.cash` + } + + return lud16 + } + const editItem = ( key: keyof ProfileMetadata, label: string, @@ -178,6 +202,59 @@ export const ProfilePage = () => { setSavingProfileMetadata(false) } + const onProfileImageChange = (event: any) => { + const file = event?.target?.files[0] + + if (file) { + setUploadingImage(true) + + const apiController = new ApiController() + + apiController.uploadMediaImage(file).then((imageResponse: PostMediaResponse) => { + if (imageResponse && imageResponse.directLink) { + setTimeout(() => { + setProfileMetadata((prev) => ({ + ...prev, + picture: imageResponse.directLink + })) + }, 200) + } else { + toast.error(`Uploading image failed. Please try again.`) + } + }) + .catch((err) => { + console.error('Error uploading image to media server.', err) + }) + .finally(() => { + setUploadingImage(false) + }) + } + } + + const progressSpinner = (show = false) => { + if (!show) return undefined + + return + } + + const generateRobotAvatar = () => { + setAvatarLoading(true) + + const robotAvatarLink = `https://robohash.org/${npub}.png?set=set3` + + setProfileMetadata((prev) => ({ + ...prev, + picture: '' + })) + + setTimeout(() => { + setProfileMetadata((prev) => ({ + ...prev, + picture: robotAvatarLink + })) + }) + } + return ( <> {isLoading && } @@ -214,10 +291,27 @@ export const ProfilePage = () => { onError={(event: any) => { event.target.src = placeholderAvatar }} + onLoad={() => { + setAvatarLoading(false) + }} className={styles.img} src={profileMetadata.picture || placeholderAvatar} alt='Profile Image' /> + + Generate Robot Avatar + + {editItem('name', 'Username')} @@ -225,6 +319,7 @@ export const ProfilePage = () => { {editItem('nip05', 'Nostr Address (nip05)')} {editItem('lud16', 'Lightning Address (lud16)')} {editItem('about', 'About', true, 4)} + {editItem('website', 'Website')} {isUsersOwnProfile && ( <> {usersPubkey && diff --git a/src/types/media.ts b/src/types/media.ts new file mode 100644 index 0000000..07e25e7 --- /dev/null +++ b/src/types/media.ts @@ -0,0 +1,19 @@ +export interface PostMediaResponse { + result: boolean + description: string + status: string + id: number + pubkey: string + url: string + directLink: string + hash: string + magnet: string + tags: any[] +} + +export interface GetMediaResponse extends PostMediaResponse {} + +export interface MediaErrorResponse { + description: string + result: boolean +} diff --git a/src/types/profile.ts b/src/types/profile.ts index 46f120f..e65d602 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -3,6 +3,7 @@ export interface ProfileMetadata { display_name?: string picture?: string about?: string + website?: string nip05?: string lud16?: string } From 413c9cc5756a7cfacb651de76d3e25d4578d9730 Mon Sep 17 00:00:00 2001 From: Davinci Date: Mon, 13 May 2024 08:02:35 +0200 Subject: [PATCH 4/9] chore: lint --- package.json | 1 + src/controllers/NostrController.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 20655c0..a4b9dd8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dev": "vite", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "dependencies": { diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index 5016ac8..27ec0e8 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -370,7 +370,7 @@ createNostrHttpAuthToken = async ( ): Promise => { const createdAt = Math.round(Date.now() / 1000) - let authEvent = new NDKEvent(undefined) + const authEvent = new NDKEvent(undefined) authEvent.kind = 27235 authEvent.tags = nostrTags authEvent.content = `sigit-${createdAt}` From 1fbc2414fe45b1825e8e35ab6c2cf379290f8572 Mon Sep 17 00:00:00 2001 From: Davinci Date: Mon, 13 May 2024 09:52:07 +0200 Subject: [PATCH 5/9] chore: removed sufficient if --- src/pages/profile/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 4ca9a4b..d444a60 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -125,9 +125,7 @@ export const ProfilePage = () => { const getLud16 = () => { let lud16 = '' - if (npub) { - if (npub) lud16 = `${npub}@npub.cash` - } + if (npub) lud16 = `${npub}@npub.cash` return lud16 } From 2221b22285def582b924ba63c851e1b4695d7443 Mon Sep 17 00:00:00 2001 From: Davinci Date: Mon, 13 May 2024 10:11:39 +0200 Subject: [PATCH 6/9] chore: media server protocol fix, removed npub cash --- src/controllers/ApiController.ts | 2 +- src/pages/profile/index.tsx | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/controllers/ApiController.ts b/src/controllers/ApiController.ts index 83fd0c7..e2cdace 100644 --- a/src/controllers/ApiController.ts +++ b/src/controllers/ApiController.ts @@ -34,7 +34,7 @@ export class ApiController { const endpointUrl = 'v1/media' const tags = [ - ['u', `${mediaUrl}/${endpointUrl}`.replace('https', 'http')], //nostrcheck api, gets http protocol because it is inside a docker container in localhost + ['u', `${mediaUrl}/${endpointUrl}`], ['method', 'POST'], ['payload', this.hashString('{}')] ] diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index d444a60..18927b0 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -83,10 +83,6 @@ export const ProfilePage = () => { metadataState as VerifiedEvent ) if (metadataContent) { - if (!metadataContent.lud16 || metadataContent.lud16.length < 1) { - metadataContent.lud16 = getLud16() - } - setProfileMetadata(metadataContent) setIsLoading(false) } @@ -107,10 +103,6 @@ export const ProfilePage = () => { const metadataContent = metadataController.extractProfileMetadataContent(metadataEvent) if (metadataContent) { - if (!metadataContent.lud16 || metadataContent.lud16.length < 1) { - metadataContent.lud16 = getLud16() - } - setProfileMetadata(metadataContent) } } @@ -122,14 +114,6 @@ export const ProfilePage = () => { } }, [isUsersOwnProfile, metadataState, pubkey, metadataController]) - const getLud16 = () => { - let lud16 = '' - - if (npub) lud16 = `${npub}@npub.cash` - - return lud16 - } - const editItem = ( key: keyof ProfileMetadata, label: string, From 8e7620201ea4252bc9b025f4d0b24930795c016a Mon Sep 17 00:00:00 2001 From: Davinci Date: Tue, 14 May 2024 14:39:05 +0200 Subject: [PATCH 7/9] fix: removing file upload, avatar by robohash --- src/controllers/ApiController.ts | 80 -------------------------------- src/pages/profile/index.tsx | 79 +++++++++++-------------------- src/types/media.ts | 19 -------- 3 files changed, 26 insertions(+), 152 deletions(-) delete mode 100644 src/controllers/ApiController.ts delete mode 100644 src/types/media.ts diff --git a/src/controllers/ApiController.ts b/src/controllers/ApiController.ts deleted file mode 100644 index e2cdace..0000000 --- a/src/controllers/ApiController.ts +++ /dev/null @@ -1,80 +0,0 @@ -import axios, { AxiosError, AxiosInstance } from "axios" -import { PostMediaResponse } from "../types/media" -import sha256 from 'crypto-js/sha256' -import Hex from 'crypto-js/enc-hex' -import { NostrController } from "." - -export class ApiController { - private mediaUrl: string = 'https://media.nquiz.io' - private api: AxiosInstance - private baseUrl: string = '' - - private nostrController = NostrController.getInstance() - - constructor(baseUrl?: string) { - this.baseUrl = baseUrl ?? this.baseUrl - - this.api = axios.create({ - withCredentials: true, - baseURL: this.baseUrl, - headers: { - 'Content-Type': 'application/json' - } - }) - } - - uploadMediaImage = async ( - file: Buffer, - ): Promise => { - const mediaUrl = this.mediaUrl - - const formData = new FormData() - const imageBlob = new Blob([file]) - formData.append('mediafile', imageBlob) - - const endpointUrl = 'v1/media' - const tags = [ - ['u', `${mediaUrl}/${endpointUrl}`], - ['method', 'POST'], - ['payload', this.hashString('{}')] - ] - - const authToken = await this.nostrController.createNostrHttpAuthToken(tags) - - console.info('Auth token created for media server.', authToken) - - return axios - .post(`${mediaUrl}/api/v1/media`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - Authorization: `Nostr ${authToken}` - } - }) - .then((res) => { - console.info( - `POST ${mediaUrl}/api/v1/media | res.data:`, - res.data - ) - // Create correct api url, because res.url is not including 'api/v1/' - if (!res || !res.data) { - console.warn('Image upload: no data returned after upload') - } - - const data = res.data - data.directLink = `${data.url.replace(mediaUrl, `${mediaUrl}/api/v1`)}` - - return data - }) - .catch((err: AxiosError) => { - console.info(`POST ${mediaUrl}/api/v1/media | err:`, err) - throw { - code: 500, - message: err.response?.data || err.response || err.message || err - } - }) - } - - hashString = (payload: any): string => { - return sha256(payload).toString(Hex) - } -} \ No newline at end of file diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 18927b0..4f64858 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -1,10 +1,13 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy' import { CircularProgress, + IconButton, + InputProps, List, ListItem, ListSubheader, TextField, + Tooltip, Typography, useTheme } from '@mui/material' @@ -23,8 +26,7 @@ import { Dispatch } from '../../store/store' import { setMetadataEvent } from '../../store/actions' import { LoadingSpinner } from '../../components/LoadingSpinner' import { LoginMethods } from '../../store/auth/types' -import { ApiController } from '../../controllers/ApiController' -import { PostMediaResponse } from '../../types/media' +import { SmartToy } from '@mui/icons-material' export const ProfilePage = () => { const theme = useTheme() @@ -45,7 +47,6 @@ export const ProfilePage = () => { const metadataState = useSelector((state: State) => state.metadata) const keys = useSelector((state: State) => state.auth?.keyPair) const { usersPubkey, loginMethod } = useSelector((state: State) => state.auth) - const [uploadingImage, setUploadingImage] = useState() const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false) @@ -118,7 +119,8 @@ export const ProfilePage = () => { key: keyof ProfileMetadata, label: string, multiline = false, - rows = 1 + rows = 1, + inputProps?: InputProps ) => ( { rows={rows} className={styles.textField} disabled={!isUsersOwnProfile} + InputProps={inputProps} onChange={(event: React.ChangeEvent) => { const { value } = event.target @@ -208,41 +211,6 @@ export const ProfilePage = () => { setSavingProfileMetadata(false) } - const onProfileImageChange = (event: any) => { - const file = event?.target?.files[0] - - if (file) { - setUploadingImage(true) - - const apiController = new ApiController() - - apiController.uploadMediaImage(file).then((imageResponse: PostMediaResponse) => { - if (imageResponse && imageResponse.directLink) { - setTimeout(() => { - setProfileMetadata((prev) => ({ - ...prev, - picture: imageResponse.directLink - })) - }, 200) - } else { - toast.error(`Uploading image failed. Please try again.`) - } - }) - .catch((err) => { - console.error('Error uploading image to media server.', err) - }) - .finally(() => { - setUploadingImage(false) - }) - } - } - - const progressSpinner = (show = false) => { - if (!show) return undefined - - return - } - const generateRobotAvatar = () => { setAvatarLoading(true) @@ -261,6 +229,21 @@ export const ProfilePage = () => { }) } + /** + * + * @returns robohash generate button, loading spinner or no button + */ + const robohashButton = () => { + if (profileMetadata?.picture?.includes('robohash')) return null + + return + {avatarLoading ? + : + + } + + } + return ( <> {isLoading && } @@ -304,20 +287,6 @@ export const ProfilePage = () => { alt='Profile Image' /> - Generate Robot Avatar - - - {nostrJoiningBlock && ( { )} + {editItem('picture', 'Picture URL', undefined, undefined, { + endAdornment: robohashButton() + })} + {editItem('name', 'Username')} {editItem('display_name', 'Display Name')} {editItem('nip05', 'Nostr Address (nip05)')} diff --git a/src/types/media.ts b/src/types/media.ts deleted file mode 100644 index 07e25e7..0000000 --- a/src/types/media.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface PostMediaResponse { - result: boolean - description: string - status: string - id: number - pubkey: string - url: string - directLink: string - hash: string - magnet: string - tags: any[] -} - -export interface GetMediaResponse extends PostMediaResponse {} - -export interface MediaErrorResponse { - description: string - result: boolean -} From e7a57c9aa10dd3c91bd4e11ad6265e049e5ccdce Mon Sep 17 00:00:00 2001 From: Davinci Date: Tue, 14 May 2024 16:48:09 +0200 Subject: [PATCH 8/9] chore: addressing comments --- src/controllers/NostrController.ts | 35 ----------------------------- src/pages/decrypt/index.tsx | 12 +++------- src/types/errors/DecryptionError.ts | 20 +++++++++++++++++ src/utils/crypto.ts | 25 ++++++++++++--------- 4 files changed, 38 insertions(+), 54 deletions(-) create mode 100644 src/types/errors/DecryptionError.ts diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index 27ec0e8..a2b0f1e 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -359,39 +359,4 @@ export class NostrController extends EventEmitter { generateDelegatedKey = (): string => { return NDKPrivateKeySigner.generate().privateKey! } - - /** - * Creates Nostr HTTP Auth token - * @param npub npub in hex format - * @param nostrTags tags to be included in the authevent (auth token) - */ -createNostrHttpAuthToken = async ( - nostrTags: string[][] = [] -): Promise => { - const createdAt = Math.round(Date.now() / 1000) - - const authEvent = new NDKEvent(undefined) - authEvent.kind = 27235 - authEvent.tags = nostrTags - authEvent.content = `sigit-${createdAt}` - authEvent.created_at = createdAt - - await this.signEvent(authEvent.rawEvent() as UnsignedEvent) - - console.info('Signed auth event') - - const base64Encoded = this.base64EncodeSignedEvent(authEvent.rawEvent()) - - return base64Encoded -} - -base64EncodeSignedEvent = (event: NostrEvent) => { - try { - const authEventSerialized = JSON.stringify(event) - const token = btoa(authEventSerialized) - return token - } catch (error) { - throw new Error('An error occurred in JSON.stringy of signedAuthEvent') - } -} } diff --git a/src/pages/decrypt/index.tsx b/src/pages/decrypt/index.tsx index 7d954dd..6b20491 100644 --- a/src/pages/decrypt/index.tsx +++ b/src/pages/decrypt/index.tsx @@ -8,6 +8,7 @@ import styles from './style.module.scss' import { toast } from 'react-toastify' import { useSearchParams } from 'react-router-dom' import axios from 'axios' +import { DecryptionError } from '../../types/errors/DecryptionError' export const DecryptZip = () => { const [searchParams] = useSearchParams() @@ -60,17 +61,10 @@ export const DecryptZip = () => { const arrayBuffer = await decryptArrayBuffer( encryptedArrayBuffer, encryptionKey - ).catch((err) => { + ).catch((err: DecryptionError) => { console.log('err in decryption:>> ', err) - if (err.message.toLowerCase().includes('expected')) { - toast.error(`The Key seems to be invalid length or format`) - } else if (err.message.includes('The JWK "alg" member was inconsistent')) { - toast.error(`The Key seems to be invalid.`) - } else { - toast.error(err.message || 'An error occurred while decrypting file.') - } - + toast.error(err.message) setIsLoading(false) return null }) diff --git a/src/types/errors/DecryptionError.ts b/src/types/errors/DecryptionError.ts new file mode 100644 index 0000000..58117ab --- /dev/null +++ b/src/types/errors/DecryptionError.ts @@ -0,0 +1,20 @@ +export class DecryptionError extends Error { + public message: string = '' + + constructor( + public inputError: any + ) { + super() + + if (inputError.message.toLowerCase().includes('expected')) { + this.message = `The decryption key length or format is invalid` + } else if (inputError.message.includes('The JWK "alg" member was inconsistent')) { + this.message = `The decryption key is invalid.` + } else { + this.message = inputError.message || 'An error occurred while decrypting file.' + } + + this.name = 'DecryptionError' + Object.setPrototypeOf(this, DecryptionError.prototype) + } +} \ No newline at end of file diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index dc55c47..dd344b3 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -4,6 +4,7 @@ import { stringToHex, uint8ArrayToHexString } from '.' +import { DecryptionError } from '../types/errors/DecryptionError' const ENCRYPTION_ALGO_NAME = 'AES-GCM' @@ -63,14 +64,18 @@ export const decryptArrayBuffer = async ( encryptedData: ArrayBuffer, key: string ) => { - const { cryptoKey, iv } = await importKey(key) - - // Decrypt the data - const decryptedData = await window.crypto.subtle.decrypt( - { name: ENCRYPTION_ALGO_NAME, iv }, - cryptoKey, - encryptedData - ) - - return decryptedData + try { + const { cryptoKey, iv } = await importKey(key) + + // Decrypt the data + const decryptedData = await window.crypto.subtle.decrypt( + { name: ENCRYPTION_ALGO_NAME, iv }, + cryptoKey, + encryptedData + ) + + return decryptedData + } catch (err) { + throw new DecryptionError(err) + } } From a1f096018c4fb2e8ad573437f29afe547e9b665a Mon Sep 17 00:00:00 2001 From: Davinci Date: Tue, 14 May 2024 16:59:13 +0200 Subject: [PATCH 9/9] chore: error message dots --- src/types/errors/DecryptionError.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/errors/DecryptionError.ts b/src/types/errors/DecryptionError.ts index 58117ab..05ee071 100644 --- a/src/types/errors/DecryptionError.ts +++ b/src/types/errors/DecryptionError.ts @@ -7,7 +7,7 @@ export class DecryptionError extends Error { super() if (inputError.message.toLowerCase().includes('expected')) { - this.message = `The decryption key length or format is invalid` + this.message = `The decryption key length or format is invalid.` } else if (inputError.message.includes('The JWK "alg" member was inconsistent')) { this.message = `The decryption key is invalid.` } else {