issue-38 #62
62
package-lock.json
generated
62
package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"@mui/icons-material": "5.15.11",
|
"@mui/icons-material": "5.15.11",
|
||||||
"@mui/lab": "5.0.0-alpha.166",
|
"@mui/lab": "5.0.0-alpha.166",
|
||||||
"@mui/material": "5.15.11",
|
"@mui/material": "5.15.11",
|
||||||
|
"@noble/hashes": "^1.4.0",
|
||||||
"@nostr-dev-kit/ndk": "2.5.0",
|
"@nostr-dev-kit/ndk": "2.5.0",
|
||||||
"@reduxjs/toolkit": "2.2.1",
|
"@reduxjs/toolkit": "2.2.1",
|
||||||
"axios": "1.6.7",
|
"axios": "1.6.7",
|
||||||
@ -1505,9 +1506,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@noble/hashes": {
|
"node_modules/@noble/hashes": {
|
||||||
"version": "1.3.1",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
||||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
},
|
},
|
||||||
@ -1598,6 +1599,17 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nostr-dev-kit/ndk/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools": {
|
"node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools": {
|
||||||
"version": "1.17.0",
|
"version": "1.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.17.0.tgz",
|
||||||
@ -1863,6 +1875,28 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@scure/bip32/node_modules/@noble/curves/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@scure/bip32/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@scure/bip39": {
|
"node_modules/@scure/bip39": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
||||||
@ -1875,6 +1909,17 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@scure/bip39/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||||
@ -4027,6 +4072,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nostr-tools/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nostr-wasm": {
|
"node_modules/nostr-wasm": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"@mui/icons-material": "5.15.11",
|
"@mui/icons-material": "5.15.11",
|
||||||
"@mui/lab": "5.0.0-alpha.166",
|
"@mui/lab": "5.0.0-alpha.166",
|
||||||
"@mui/material": "5.15.11",
|
"@mui/material": "5.15.11",
|
||||||
|
"@noble/hashes": "^1.4.0",
|
||||||
"@nostr-dev-kit/ndk": "2.5.0",
|
"@nostr-dev-kit/ndk": "2.5.0",
|
||||||
"@reduxjs/toolkit": "2.2.1",
|
"@reduxjs/toolkit": "2.2.1",
|
||||||
"axios": "1.6.7",
|
"axios": "1.6.7",
|
||||||
|
@ -10,26 +10,28 @@ import {
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { setAuthState } from '../../store/actions'
|
import { setAuthState, setMetadataEvent } from '../../store/actions'
|
||||||
import { State } from '../../store/rootReducer'
|
import { State } from '../../store/rootReducer'
|
||||||
import { Dispatch } from '../../store/store'
|
import { Dispatch } from '../../store/store'
|
||||||
import Username from '../username'
|
import Username from '../username'
|
||||||
|
|
||||||
import { Link, useNavigate } from 'react-router-dom'
|
import { Link, useNavigate } from 'react-router-dom'
|
||||||
import { NostrController } from '../../controllers'
|
|
||||||
import {
|
import {
|
||||||
appPrivateRoutes,
|
appPrivateRoutes,
|
||||||
appPublicRoutes,
|
appPublicRoutes,
|
||||||
getProfileRoute
|
getProfileRoute
|
||||||
} from '../../routes'
|
} from '../../routes'
|
||||||
|
import { MetadataController, NostrController } from '../../controllers'
|
||||||
import {
|
import {
|
||||||
clearAuthToken,
|
clearAuthToken,
|
||||||
getRoboHashPicture,
|
clearState,
|
||||||
saveNsecBunkerDelegatedKey,
|
saveNsecBunkerDelegatedKey,
|
||||||
shorten
|
shorten
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
|
|
||||||
|
const metadataController = new MetadataController()
|
||||||
|
|
||||||
export const AppBar = () => {
|
export const AppBar = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
@ -43,17 +45,21 @@ export const AppBar = () => {
|
|||||||
const metadataState = useSelector((state: State) => state.metadata)
|
const metadataState = useSelector((state: State) => state.metadata)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (metadataState && metadataState.content) {
|
if (metadataState) {
|
||||||
const { picture, display_name, name } = JSON.parse(metadataState.content)
|
if (metadataState.content) {
|
||||||
const pubkey = authState?.usersPubkey || ''
|
const { picture, display_name, name } = JSON.parse(
|
||||||
|
metadataState.content
|
||||||
|
)
|
||||||
|
|
||||||
if (picture) {
|
if (picture) {
|
||||||
setUserAvatar(picture)
|
setUserAvatar(picture)
|
||||||
} else {
|
|
||||||
setUserAvatar(getRoboHashPicture(pubkey))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setUsername(shorten(display_name || name || '', 7))
|
setUsername(shorten(display_name || name || '', 7))
|
||||||
|
} else {
|
||||||
|
setUserAvatar('')
|
||||||
|
setUsername('')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [metadataState])
|
}, [metadataState])
|
||||||
|
|
||||||
@ -76,6 +82,7 @@ export const AppBar = () => {
|
|||||||
handleCloseUserMenu()
|
handleCloseUserMenu()
|
||||||
dispatch(
|
dispatch(
|
||||||
setAuthState({
|
setAuthState({
|
||||||
|
keyPair: undefined,
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
usersPubkey: undefined,
|
usersPubkey: undefined,
|
||||||
loginMethod: undefined,
|
loginMethod: undefined,
|
||||||
@ -83,8 +90,11 @@ export const AppBar = () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dispatch(setMetadataEvent(metadataController.getEmptyMetadataEvent()))
|
||||||
|
|
||||||
// clear authToken saved in local storage
|
// clear authToken saved in local storage
|
||||||
clearAuthToken()
|
clearAuthToken()
|
||||||
|
clearState()
|
||||||
|
|
||||||
// update nsecBunker delegated key after logout
|
// update nsecBunker delegated key after logout
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
base64DecodeAuthToken,
|
base64DecodeAuthToken,
|
||||||
base64EncodeSignedEvent,
|
base64EncodeSignedEvent,
|
||||||
getAuthToken,
|
getAuthToken,
|
||||||
|
getRoboHashPicture,
|
||||||
getVisitedLink,
|
getVisitedLink,
|
||||||
saveAuthToken,
|
saveAuthToken,
|
||||||
compareObjects
|
compareObjects
|
||||||
@ -42,7 +43,15 @@ export class AuthController {
|
|||||||
store.dispatch(setMetadataEvent(event))
|
store.dispatch(setMetadataEvent(event))
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('Error occurred while finding metadata', err)
|
console.warn('Error occurred while finding metadata', err)
|
||||||
|
|
||||||
|
const emptyMetadata = this.metadataController.getEmptyMetadataEvent()
|
||||||
|
|
||||||
|
emptyMetadata.content = JSON.stringify({
|
||||||
|
picture: getRoboHashPicture(pubkey)
|
||||||
|
})
|
||||||
|
|
||||||
|
store.dispatch(setMetadataEvent(emptyMetadata))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Nostr uses unix timestamps
|
// Nostr uses unix timestamps
|
||||||
|
@ -23,6 +23,18 @@ export class MetadataController {
|
|||||||
this.nostrController = NostrController.getInstance()
|
this.nostrController = NostrController.getInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getEmptyMetadataEvent = (): Event => {
|
||||||
|
return {
|
||||||
|
content: '',
|
||||||
|
created_at: new Date().valueOf(),
|
||||||
|
id: '',
|
||||||
|
kind: 0,
|
||||||
|
pubkey: '',
|
||||||
|
sig: '',
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public findMetadata = async (hexKey: string) => {
|
public findMetadata = async (hexKey: string) => {
|
||||||
const eventFilter: Filter = {
|
const eventFilter: Filter = {
|
||||||
kinds: [kinds.Metadata],
|
kinds: [kinds.Metadata],
|
||||||
|
@ -326,15 +326,18 @@ export class NostrController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.privateKey) {
|
if (loginMethod === LoginMethods.privateKey) {
|
||||||
const keyPair = (store.getState().auth as AuthState).keyPair
|
const keys = (store.getState().auth as AuthState).keyPair
|
||||||
|
|
||||||
if (!keyPair) {
|
if (!keys) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const encrypted = await nip04.encrypt(keyPair.private, receiver, content)
|
const { private: nsec } = keys
|
||||||
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
const encrypted = await nip04.encrypt(privateKey, receiver, content)
|
||||||
return encrypted
|
return encrypted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,13 +4,15 @@ import { useEffect, useState } from 'react'
|
|||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
import { Outlet } from 'react-router-dom'
|
import { Outlet } from 'react-router-dom'
|
||||||
import { AppBar } from '../components/AppBar/AppBar'
|
import { AppBar } from '../components/AppBar/AppBar'
|
||||||
import { restoreState, setAuthState } from '../store/actions'
|
import { restoreState, setAuthState, setMetadataEvent } from '../store/actions'
|
||||||
import { clearAuthToken, loadState, saveNsecBunkerDelegatedKey } from '../utils'
|
import { clearAuthToken, clearState, loadState, saveNsecBunkerDelegatedKey } from '../utils'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||||
import { Dispatch } from '../store/store'
|
import { Dispatch } from '../store/store'
|
||||||
import { NostrController } from '../controllers'
|
import { MetadataController, NostrController } from '../controllers'
|
||||||
import { LoginMethods } from '../store/auth/types'
|
import { LoginMethods } from '../store/auth/types'
|
||||||
|
|
||||||
|
const metadataController = new MetadataController()
|
||||||
|
|
||||||
export const MainLayout = () => {
|
export const MainLayout = () => {
|
||||||
const dispatch: Dispatch = useDispatch()
|
const dispatch: Dispatch = useDispatch()
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -19,6 +21,7 @@ export const MainLayout = () => {
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setAuthState({
|
setAuthState({
|
||||||
|
keyPair: undefined,
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
usersPubkey: undefined,
|
usersPubkey: undefined,
|
||||||
loginMethod: undefined,
|
loginMethod: undefined,
|
||||||
@ -26,8 +29,13 @@ export const MainLayout = () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setMetadataEvent(metadataController.getEmptyMetadataEvent())
|
||||||
|
)
|
||||||
|
|
||||||
// clear authToken saved in local storage
|
// clear authToken saved in local storage
|
||||||
clearAuthToken()
|
clearAuthToken()
|
||||||
|
clearState()
|
||||||
|
|
||||||
// update nsecBunker delegated key
|
// update nsecBunker delegated key
|
||||||
const newDelegatedKey =
|
const newDelegatedKey =
|
||||||
|
@ -489,6 +489,11 @@ const DisplayUser = ({
|
|||||||
})
|
})
|
||||||
}, [users])
|
}, [users])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use robohash if any of the users images fail to load
|
||||||
|
* @param event img tag onError event
|
||||||
|
* @param pubkey of the user
|
||||||
|
*/
|
||||||
const imageLoadError = (event: any, pubkey: string) => {
|
const imageLoadError = (event: any, pubkey: string) => {
|
||||||
event.target.src = getRoboHashPicture(pubkey)
|
event.target.src = getRoboHashPicture(pubkey)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import { setMetadataEvent } from '../../store/actions'
|
|||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { LoginMethods } from '../../store/auth/types'
|
import { LoginMethods } from '../../store/auth/types'
|
||||||
import { SmartToy } from '@mui/icons-material'
|
import { SmartToy } from '@mui/icons-material'
|
||||||
import { getRoboHashPicture } from '../../utils'
|
|
||||||
|
|
||||||
export const ProfilePage = () => {
|
export const ProfilePage = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@ -281,14 +280,11 @@ export const ProfilePage = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
onError={(event: any) => {
|
|
||||||
event.target.src = npub ? getRoboHashPicture(npub) : ''
|
|
||||||
}}
|
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
setAvatarLoading(false)
|
setAvatarLoading(false)
|
||||||
}}
|
}}
|
||||||
className={styles.img}
|
className={styles.img}
|
||||||
src={profileMetadata.picture || placeholderAvatar}
|
src={profileMetadata.picture}
|
||||||
alt="Profile Image"
|
alt="Profile Image"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ export const VerifyPage = () => {
|
|||||||
|
|
||||||
const imageLoadError = (event: any, pubkey: string) => {
|
const imageLoadError = (event: any, pubkey: string) => {
|
||||||
const npub = hexToNpub(pubkey)
|
const npub = hexToNpub(pubkey)
|
||||||
event.target.src = npub
|
event.target.src = getRoboImageUrl(npub)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRoboImageUrl = (pubkey: string) => {
|
const getRoboImageUrl = (pubkey: string) => {
|
||||||
|
@ -22,6 +22,10 @@ export const loadState = (): State | undefined => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const clearState = () => {
|
||||||
|
localStorage.removeItem('state')
|
||||||
|
}
|
||||||
|
|
||||||
export const saveNsecBunkerDelegatedKey = (privateKey: string) => {
|
export const saveNsecBunkerDelegatedKey = (privateKey: string) => {
|
||||||
localStorage.setItem('nsecbunker-delegated-key', privateKey)
|
localStorage.setItem('nsecbunker-delegated-key', privateKey)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user