chore(refactor): replace authContoller with useAuth hook
This commit is contained in:
parent
2248128001
commit
0cc1a32059
15
src/App.tsx
15
src/App.tsx
@ -1,17 +1,21 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useAppSelector } from './hooks'
|
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { AuthController } from './controllers'
|
|
||||||
|
import { useAppSelector, useAuth } from './hooks'
|
||||||
|
|
||||||
import { MainLayout } from './layouts/Main'
|
import { MainLayout } from './layouts/Main'
|
||||||
|
|
||||||
import { appPrivateRoutes, appPublicRoutes } from './routes'
|
import { appPrivateRoutes, appPublicRoutes } from './routes'
|
||||||
import './App.scss'
|
|
||||||
import {
|
import {
|
||||||
privateRoutes,
|
privateRoutes,
|
||||||
publicRoutes,
|
publicRoutes,
|
||||||
recursiveRouteRenderer
|
recursiveRouteRenderer
|
||||||
} from './routes/util'
|
} from './routes/util'
|
||||||
|
|
||||||
|
import './App.scss'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
const { checkSession } = useAuth()
|
||||||
const authState = useAppSelector((state) => state.auth)
|
const authState = useAppSelector((state) => state.auth)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -22,9 +26,8 @@ const App = () => {
|
|||||||
window.location.hostname = 'localhost'
|
window.location.hostname = 'localhost'
|
||||||
}
|
}
|
||||||
|
|
||||||
const authController = new AuthController()
|
checkSession()
|
||||||
authController.checkSession()
|
}, [checkSession])
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleRootRedirect = () => {
|
const handleRootRedirect = () => {
|
||||||
if (authState.loggedIn) return appPrivateRoutes.homePage
|
if (authState.loggedIn) return appPrivateRoutes.homePage
|
||||||
|
@ -1,153 +0,0 @@
|
|||||||
import { EventTemplate } from 'nostr-tools'
|
|
||||||
import { MetadataController, NostrController } from '.'
|
|
||||||
import { appPrivateRoutes } from '../routes'
|
|
||||||
import {
|
|
||||||
setAuthState,
|
|
||||||
setMetadataEvent,
|
|
||||||
setRelayMapAction
|
|
||||||
} from '../store/actions'
|
|
||||||
import store from '../store/store'
|
|
||||||
import { SignedEvent } from '../types'
|
|
||||||
import {
|
|
||||||
base64DecodeAuthToken,
|
|
||||||
base64EncodeSignedEvent,
|
|
||||||
compareObjects,
|
|
||||||
getAuthToken,
|
|
||||||
getRelayMap,
|
|
||||||
saveAuthToken,
|
|
||||||
unixNow
|
|
||||||
} from '../utils'
|
|
||||||
|
|
||||||
export class AuthController {
|
|
||||||
private nostrController: NostrController
|
|
||||||
private metadataController: MetadataController
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.nostrController = NostrController.getInstance()
|
|
||||||
this.metadataController = MetadataController.getInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function will authenticate user by signing an auth event
|
|
||||||
* which is done by calling the sign() function, where appropriate
|
|
||||||
* method will be chosen (extension or keys)
|
|
||||||
*
|
|
||||||
* @param pubkey of the user trying to login
|
|
||||||
* @returns url to redirect if authentication successfull
|
|
||||||
* or error if otherwise
|
|
||||||
*/
|
|
||||||
async authAndGetMetadataAndRelaysMap(pubkey: string) {
|
|
||||||
const emptyMetadata = this.metadataController.getEmptyMetadataEvent()
|
|
||||||
|
|
||||||
this.metadataController
|
|
||||||
.findMetadata(pubkey)
|
|
||||||
.then((event) => {
|
|
||||||
if (event) {
|
|
||||||
store.dispatch(setMetadataEvent(event))
|
|
||||||
} else {
|
|
||||||
store.dispatch(setMetadataEvent(emptyMetadata))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.warn('Error occurred while finding metadata', err)
|
|
||||||
|
|
||||||
store.dispatch(setMetadataEvent(emptyMetadata))
|
|
||||||
})
|
|
||||||
|
|
||||||
// Nostr uses unix timestamps
|
|
||||||
const timestamp = unixNow()
|
|
||||||
const { href } = window.location
|
|
||||||
|
|
||||||
const authEvent: EventTemplate = {
|
|
||||||
kind: 27235,
|
|
||||||
tags: [
|
|
||||||
['u', href],
|
|
||||||
['method', 'GET']
|
|
||||||
],
|
|
||||||
content: '',
|
|
||||||
created_at: timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
const signedAuthEvent = await this.nostrController.signEvent(authEvent)
|
|
||||||
this.createAndSaveAuthToken(signedAuthEvent)
|
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
setAuthState({
|
|
||||||
loggedIn: true,
|
|
||||||
usersPubkey: pubkey
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const relayMap = await getRelayMap(pubkey)
|
|
||||||
|
|
||||||
if (Object.keys(relayMap).length < 1) {
|
|
||||||
// Navigate user to relays page if relay map is empty
|
|
||||||
return Promise.resolve(appPrivateRoutes.relays)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store.getState().auth.loggedIn) {
|
|
||||||
if (!compareObjects(store.getState().relays?.map, relayMap.map))
|
|
||||||
store.dispatch(setRelayMapAction(relayMap.map))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This block was added before we started using the `nostr-login` package
|
|
||||||
* At this point it seems it's not needed anymore and it's even blocking the flow (reloading on /verify)
|
|
||||||
* TODO to remove this if app works fine
|
|
||||||
*/
|
|
||||||
// const currentLocation = window.location.hash.replace('#', '')
|
|
||||||
|
|
||||||
// if (!Object.values(appPrivateRoutes).includes(currentLocation)) {
|
|
||||||
// // Since verify is both public and private route, we don't use the `visitedLink`
|
|
||||||
// // value for it. Otherwise, when linking to /verify/:id we get redirected
|
|
||||||
// // to the root `/`
|
|
||||||
// if (currentLocation.includes(appPublicRoutes.verify)) {
|
|
||||||
// return Promise.resolve(currentLocation)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // User did change the location to one of the private routes
|
|
||||||
// const visitedLink = getVisitedLink()
|
|
||||||
//
|
|
||||||
// if (visitedLink) {
|
|
||||||
// const { pathname, search } = visitedLink
|
|
||||||
//
|
|
||||||
// return Promise.resolve(`${pathname}${search}`)
|
|
||||||
// } else {
|
|
||||||
// // Navigate user in
|
|
||||||
// return Promise.resolve(appPrivateRoutes.homePage)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
checkSession() {
|
|
||||||
const savedAuthToken = getAuthToken()
|
|
||||||
|
|
||||||
if (savedAuthToken) {
|
|
||||||
const signedEvent = base64DecodeAuthToken(savedAuthToken)
|
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
setAuthState({
|
|
||||||
loggedIn: true,
|
|
||||||
usersPubkey: signedEvent.pubkey
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
store.dispatch(
|
|
||||||
setAuthState({
|
|
||||||
loggedIn: false,
|
|
||||||
usersPubkey: undefined
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private createAndSaveAuthToken(signedAuthEvent: SignedEvent) {
|
|
||||||
const base64Encoded = base64EncodeSignedEvent(signedAuthEvent)
|
|
||||||
|
|
||||||
// save newly created auth token (base64 nostr singed event) in local storage along with expiry time
|
|
||||||
saveAuthToken(base64Encoded)
|
|
||||||
return base64Encoded
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,6 @@
|
|||||||
export * from './store'
|
export * from './store'
|
||||||
|
export * from './useAuth'
|
||||||
export * from './useDidMount'
|
export * from './useDidMount'
|
||||||
export * from './useDvm'
|
export * from './useDvm'
|
||||||
|
export * from './useLogout'
|
||||||
export * from './useNDKContext'
|
export * from './useNDKContext'
|
||||||
|
127
src/hooks/useAuth.ts
Normal file
127
src/hooks/useAuth.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { Event, EventTemplate } from 'nostr-tools'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import { NostrController } from '../controllers'
|
||||||
|
import { appPrivateRoutes } from '../routes'
|
||||||
|
import {
|
||||||
|
setAuthState,
|
||||||
|
setMetadataEvent,
|
||||||
|
setRelayMapAction
|
||||||
|
} from '../store/actions'
|
||||||
|
import {
|
||||||
|
base64DecodeAuthToken,
|
||||||
|
compareObjects,
|
||||||
|
createAndSaveAuthToken,
|
||||||
|
getAuthToken,
|
||||||
|
getEmptyMetadataEvent,
|
||||||
|
getRelayMap,
|
||||||
|
unixNow
|
||||||
|
} from '../utils'
|
||||||
|
import { useAppDispatch, useAppSelector } from './store'
|
||||||
|
import { useNDKContext } from './useNDKContext'
|
||||||
|
|
||||||
|
export const useAuth = () => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const { findMetadata } = useNDKContext()
|
||||||
|
|
||||||
|
const { auth: authState, relays: relaysState } = useAppSelector(
|
||||||
|
(state) => state
|
||||||
|
)
|
||||||
|
|
||||||
|
const checkSession = useCallback(() => {
|
||||||
|
const savedAuthToken = getAuthToken()
|
||||||
|
|
||||||
|
if (savedAuthToken) {
|
||||||
|
const signedEvent = base64DecodeAuthToken(savedAuthToken)
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setAuthState({
|
||||||
|
loggedIn: true,
|
||||||
|
usersPubkey: signedEvent.pubkey
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setAuthState({
|
||||||
|
loggedIn: false,
|
||||||
|
usersPubkey: undefined
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function will authenticate user by signing an auth event
|
||||||
|
* which is done by calling the sign() function, where appropriate
|
||||||
|
* method will be chosen (extension or keys)
|
||||||
|
*
|
||||||
|
* @param pubkey of the user trying to login
|
||||||
|
* @returns url to redirect if authentication successfull
|
||||||
|
* or error if otherwise
|
||||||
|
*/
|
||||||
|
const authAndGetMetadataAndRelaysMap = useCallback(
|
||||||
|
async (pubkey: string) => {
|
||||||
|
const emptyMetadata = getEmptyMetadataEvent()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const profile = await findMetadata(pubkey, {}, true)
|
||||||
|
|
||||||
|
if (profile && profile.profileEvent) {
|
||||||
|
const event: Event = JSON.parse(profile.profileEvent)
|
||||||
|
dispatch(setMetadataEvent(event))
|
||||||
|
} else {
|
||||||
|
dispatch(setMetadataEvent(emptyMetadata))
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Error occurred while finding metadata', err)
|
||||||
|
dispatch(setMetadataEvent(emptyMetadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = unixNow()
|
||||||
|
const { href } = window.location
|
||||||
|
|
||||||
|
const authEvent: EventTemplate = {
|
||||||
|
kind: 27235,
|
||||||
|
tags: [
|
||||||
|
['u', href],
|
||||||
|
['method', 'GET']
|
||||||
|
],
|
||||||
|
content: '',
|
||||||
|
created_at: timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
const nostrController = NostrController.getInstance()
|
||||||
|
const signedAuthEvent = await nostrController.signEvent(authEvent)
|
||||||
|
createAndSaveAuthToken(signedAuthEvent)
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setAuthState({
|
||||||
|
loggedIn: true,
|
||||||
|
usersPubkey: pubkey
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const relayMap = await getRelayMap(pubkey)
|
||||||
|
|
||||||
|
if (Object.keys(relayMap).length < 1) {
|
||||||
|
// Navigate user to relays page if relay map is empty
|
||||||
|
return appPrivateRoutes.relays
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
authState.loggedIn &&
|
||||||
|
!compareObjects(relaysState?.map, relayMap.map)
|
||||||
|
) {
|
||||||
|
dispatch(setRelayMapAction(relayMap.map))
|
||||||
|
}
|
||||||
|
|
||||||
|
return appPrivateRoutes.homePage
|
||||||
|
},
|
||||||
|
[dispatch, findMetadata, authState, relaysState]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
authAndGetMetadataAndRelaysMap,
|
||||||
|
checkSession
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,18 @@
|
|||||||
import { Event, getPublicKey, kinds, nip19 } from 'nostr-tools'
|
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { Outlet, useNavigate, useSearchParams } from 'react-router-dom'
|
import { Outlet, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { Event, getPublicKey, kinds, nip19 } from 'nostr-tools'
|
||||||
|
|
||||||
|
import { init as initNostrLogin } from 'nostr-login'
|
||||||
|
import { NostrLoginAuthOptions } from 'nostr-login/dist/types'
|
||||||
|
|
||||||
import { AppBar } from '../components/AppBar/AppBar'
|
import { AppBar } from '../components/AppBar/AppBar'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||||
import {
|
|
||||||
AuthController,
|
import { MetadataController, NostrController } from '../controllers'
|
||||||
MetadataController,
|
|
||||||
NostrController
|
import { useAppDispatch, useAppSelector, useAuth, useLogout } from '../hooks'
|
||||||
} from '../controllers'
|
|
||||||
import {
|
import {
|
||||||
restoreState,
|
restoreState,
|
||||||
setMetadataEvent,
|
setMetadataEvent,
|
||||||
@ -16,25 +21,25 @@ import {
|
|||||||
updateNostrLoginAuthMethod,
|
updateNostrLoginAuthMethod,
|
||||||
updateUserAppData
|
updateUserAppData
|
||||||
} from '../store/actions'
|
} from '../store/actions'
|
||||||
|
import { LoginMethod } from '../store/auth/types'
|
||||||
import { setUserRobotImage } from '../store/userRobotImage/action'
|
import { setUserRobotImage } from '../store/userRobotImage/action'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getRoboHashPicture,
|
getRoboHashPicture,
|
||||||
getUsersAppData,
|
getUsersAppData,
|
||||||
loadState,
|
loadState,
|
||||||
subscribeForSigits
|
subscribeForSigits
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { useAppDispatch, useAppSelector } from '../hooks'
|
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { useLogout } from '../hooks/useLogout'
|
|
||||||
import { LoginMethod } from '../store/auth/types'
|
|
||||||
import { NostrLoginAuthOptions } from 'nostr-login/dist/types'
|
|
||||||
import { init as initNostrLogin } from 'nostr-login'
|
|
||||||
|
|
||||||
export const MainLayout = () => {
|
export const MainLayout = () => {
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const logout = useLogout()
|
const logout = useLogout()
|
||||||
|
const { authAndGetMetadataAndRelaysMap } = useAuth()
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`)
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`)
|
||||||
const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
|
const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
|
||||||
@ -59,13 +64,11 @@ export const MainLayout = () => {
|
|||||||
|
|
||||||
const login = useCallback(async () => {
|
const login = useCallback(async () => {
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
const authController = new AuthController()
|
|
||||||
const pubkey = await nostrController.capturePublicKey()
|
const pubkey = await nostrController.capturePublicKey()
|
||||||
|
|
||||||
dispatch(updateLoginMethod(LoginMethod.nostrLogin))
|
dispatch(updateLoginMethod(LoginMethod.nostrLogin))
|
||||||
|
|
||||||
const redirectPath =
|
const redirectPath = await authAndGetMetadataAndRelaysMap(pubkey)
|
||||||
await authController.authAndGetMetadataAndRelaysMap(pubkey)
|
|
||||||
|
|
||||||
if (redirectPath) {
|
if (redirectPath) {
|
||||||
navigateAfterLogin(redirectPath)
|
navigateAfterLogin(redirectPath)
|
||||||
@ -105,10 +108,7 @@ export const MainLayout = () => {
|
|||||||
)
|
)
|
||||||
dispatch(updateLoginMethod(LoginMethod.privateKey))
|
dispatch(updateLoginMethod(LoginMethod.privateKey))
|
||||||
|
|
||||||
const authController = new AuthController()
|
authAndGetMetadataAndRelaysMap(publickey).catch((err) => {
|
||||||
authController
|
|
||||||
.authAndGetMetadataAndRelaysMap(publickey)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Error occurred in authentication: ' + err)
|
console.error('Error occurred in authentication: ' + err)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { Button, Divider, TextField } from '@mui/material'
|
import { Button, Divider, TextField } from '@mui/material'
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { useAppDispatch } from '../../hooks/store'
|
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
|
||||||
import { AuthController } from '../../controllers'
|
|
||||||
import { updateKeyPair, updateLoginMethod } from '../../store/actions'
|
|
||||||
import { KeyboardCode } from '../../types'
|
|
||||||
import { LoginMethod } from '../../store/auth/types'
|
|
||||||
import { hexToBytes } from '@noble/hashes/utils'
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
import { getPublicKey, nip19 } from 'nostr-tools'
|
||||||
|
|
||||||
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
|
import { useAppDispatch, useAuth } from '../../hooks'
|
||||||
|
import { updateKeyPair, updateLoginMethod } from '../../store/actions'
|
||||||
|
import { LoginMethod } from '../../store/auth/types'
|
||||||
|
import { KeyboardCode } from '../../types'
|
||||||
|
|
||||||
import styles from './styles.module.scss'
|
import styles from './styles.module.scss'
|
||||||
|
|
||||||
export const Nostr = () => {
|
export const Nostr = () => {
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
const { authAndGetMetadataAndRelaysMap } = useAuth()
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const authController = new AuthController()
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState('')
|
||||||
@ -102,12 +102,12 @@ export const Nostr = () => {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
||||||
|
|
||||||
const redirectPath = await authController
|
const redirectPath = await authAndGetMetadataAndRelaysMap(publickey).catch(
|
||||||
.authAndGetMetadataAndRelaysMap(publickey)
|
(err) => {
|
||||||
.catch((err) => {
|
|
||||||
toast.error('Error occurred in authentication: ' + err)
|
toast.error('Error occurred in authentication: ' + err)
|
||||||
return null
|
return null
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (redirectPath) navigateAfterLogin(redirectPath)
|
if (redirectPath) navigateAfterLogin(redirectPath)
|
||||||
|
|
||||||
|
44
src/utils/auth.ts
Normal file
44
src/utils/auth.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Event } from 'nostr-tools'
|
||||||
|
import { SignedEvent } from '../types'
|
||||||
|
import { saveAuthToken } from './localStorage'
|
||||||
|
|
||||||
|
export const base64EncodeSignedEvent = (event: SignedEvent) => {
|
||||||
|
try {
|
||||||
|
const authEventSerialized = JSON.stringify(event)
|
||||||
|
const token = btoa(authEventSerialized)
|
||||||
|
return token
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('An error occurred in JSON.stringify of signedAuthEvent')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const base64DecodeAuthToken = (authToken: string): SignedEvent => {
|
||||||
|
const decodedToken = atob(authToken)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const signedEvent = JSON.parse(decodedToken)
|
||||||
|
return signedEvent
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('An error occurred in JSON.parse of the auth token')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createAndSaveAuthToken = (signedAuthEvent: SignedEvent) => {
|
||||||
|
const base64Encoded = base64EncodeSignedEvent(signedAuthEvent)
|
||||||
|
|
||||||
|
// save newly created auth token (base64 nostr signed event) in local storage along with expiry time
|
||||||
|
saveAuthToken(base64Encoded)
|
||||||
|
return base64Encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEmptyMetadataEvent = (pubkey?: string): Event => {
|
||||||
|
return {
|
||||||
|
content: '',
|
||||||
|
created_at: new Date().valueOf(),
|
||||||
|
id: '',
|
||||||
|
kind: 0,
|
||||||
|
pubkey: pubkey || '',
|
||||||
|
sig: '',
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
}
|
140
src/utils/dvm.ts
140
src/utils/dvm.ts
@ -1,140 +1,10 @@
|
|||||||
import { EventTemplate, Filter, kinds, nip19 } from 'nostr-tools'
|
|
||||||
import { compareObjects, queryNip05, unixNow } from '.'
|
|
||||||
import {
|
|
||||||
MetadataController,
|
|
||||||
NostrController,
|
|
||||||
relayController
|
|
||||||
} from '../controllers'
|
|
||||||
import { NostrJoiningBlock, RelayInfoObject } from '../types'
|
|
||||||
import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
|
import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
|
||||||
import store from '../store/store'
|
import { EventTemplate } from 'nostr-tools'
|
||||||
|
import { compareObjects, unixNow } from '.'
|
||||||
|
import { NostrController, relayController } from '../controllers'
|
||||||
import { setRelayInfoAction } from '../store/actions'
|
import { setRelayInfoAction } from '../store/actions'
|
||||||
|
import store from '../store/store'
|
||||||
export const getNostrJoiningBlockNumber = async (
|
import { RelayInfoObject } from '../types'
|
||||||
hexKey: string
|
|
||||||
): Promise<NostrJoiningBlock | null> => {
|
|
||||||
const metadataController = MetadataController.getInstance()
|
|
||||||
|
|
||||||
const relaySet = await metadataController.findRelayListMetadata(hexKey)
|
|
||||||
|
|
||||||
const userRelays: string[] = []
|
|
||||||
|
|
||||||
// find user's relays
|
|
||||||
if (relaySet.write.length > 0) {
|
|
||||||
userRelays.push(...relaySet.write)
|
|
||||||
} else {
|
|
||||||
const metadata = await metadataController.findMetadata(hexKey)
|
|
||||||
if (!metadata) return null
|
|
||||||
|
|
||||||
const metadataContent =
|
|
||||||
metadataController.extractProfileMetadataContent(metadata)
|
|
||||||
|
|
||||||
if (metadataContent?.nip05) {
|
|
||||||
const nip05Profile = await queryNip05(metadataContent.nip05)
|
|
||||||
|
|
||||||
if (nip05Profile && nip05Profile.pubkey === hexKey) {
|
|
||||||
userRelays.push(...nip05Profile.relays)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userRelays.length === 0) return null
|
|
||||||
|
|
||||||
// filter for finding user's first kind 0 event
|
|
||||||
const eventFilter: Filter = {
|
|
||||||
kinds: [kinds.Metadata],
|
|
||||||
authors: [hexKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
// find user's kind 0 event published on user's relays
|
|
||||||
const event = await relayController.fetchEvent(eventFilter, userRelays)
|
|
||||||
|
|
||||||
if (event) {
|
|
||||||
const { created_at } = event
|
|
||||||
|
|
||||||
// initialize job request
|
|
||||||
const jobEventTemplate: EventTemplate = {
|
|
||||||
content: '',
|
|
||||||
created_at: unixNow(),
|
|
||||||
kind: 68001,
|
|
||||||
tags: [
|
|
||||||
['i', `${created_at * 1000}`],
|
|
||||||
['j', 'blockChain-block-number']
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const nostrController = NostrController.getInstance()
|
|
||||||
|
|
||||||
// sign job request event
|
|
||||||
const jobSignedEvent = await nostrController.signEvent(jobEventTemplate)
|
|
||||||
|
|
||||||
const relays = [
|
|
||||||
'wss://relay.damus.io',
|
|
||||||
'wss://relay.primal.net',
|
|
||||||
'wss://relayable.org'
|
|
||||||
]
|
|
||||||
|
|
||||||
await relayController.publish(jobSignedEvent, relays).catch((err) => {
|
|
||||||
console.error(
|
|
||||||
'Error occurred in publish blockChain-block-number DVM job',
|
|
||||||
err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const subscribeWithTimeout = (
|
|
||||||
subscription: NDKSubscription,
|
|
||||||
timeoutMs: number
|
|
||||||
): Promise<string> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const eventHandler = (event: NDKEvent) => {
|
|
||||||
subscription.stop()
|
|
||||||
resolve(event.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription.on('event', eventHandler)
|
|
||||||
|
|
||||||
// Set up a timeout to stop the subscription after a specified time
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
subscription.stop() // Stop the subscription
|
|
||||||
reject(new Error('Subscription timed out')) // Reject the promise with a timeout error
|
|
||||||
}, timeoutMs)
|
|
||||||
|
|
||||||
// Handle subscription close event
|
|
||||||
subscription.on('close', () => clearTimeout(timeout))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const dvmNDK = new NDK({
|
|
||||||
explicitRelayUrls: relays
|
|
||||||
})
|
|
||||||
|
|
||||||
await dvmNDK.connect(2000)
|
|
||||||
|
|
||||||
// filter for getting DVM job's result
|
|
||||||
const sub = dvmNDK.subscribe({
|
|
||||||
kinds: [68002 as number],
|
|
||||||
'#e': [jobSignedEvent.id],
|
|
||||||
'#p': [jobSignedEvent.pubkey]
|
|
||||||
})
|
|
||||||
|
|
||||||
// asynchronously get block number from dvm job with 20 seconds timeout
|
|
||||||
const dvmJobResult = await subscribeWithTimeout(sub, 20000)
|
|
||||||
|
|
||||||
const encodedEventPointer = nip19.neventEncode({
|
|
||||||
id: event.id,
|
|
||||||
relays: userRelays,
|
|
||||||
author: event.pubkey,
|
|
||||||
kind: event.kind
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
block: parseInt(dvmJobResult),
|
|
||||||
encodedEventPointer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets information about relays into relays.info app state.
|
* Sets information about relays into relays.info app state.
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
export * from './auth'
|
||||||
|
export * from './const'
|
||||||
export * from './crypto'
|
export * from './crypto'
|
||||||
export * from './dvm'
|
export * from './dvm'
|
||||||
export * from './hash'
|
export * from './hash'
|
||||||
@ -11,4 +13,3 @@ export * from './string'
|
|||||||
export * from './url'
|
export * from './url'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
export * from './zip'
|
export * from './zip'
|
||||||
export * from './const'
|
|
||||||
|
@ -199,27 +199,6 @@ export const queryNip05 = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const base64EncodeSignedEvent = (event: SignedEvent) => {
|
|
||||||
try {
|
|
||||||
const authEventSerialized = JSON.stringify(event)
|
|
||||||
const token = btoa(authEventSerialized)
|
|
||||||
return token
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('An error occurred in JSON.stringify of signedAuthEvent')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const base64DecodeAuthToken = (authToken: string): SignedEvent => {
|
|
||||||
const decodedToken = atob(authToken)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const signedEvent = JSON.parse(decodedToken)
|
|
||||||
return signedEvent
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('An error occurred in JSON.parse of the auth token')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pubkey in hex or npub format
|
* @param pubkey in hex or npub format
|
||||||
* @returns robohash.org url for the avatar
|
* @returns robohash.org url for the avatar
|
||||||
@ -985,7 +964,7 @@ export const sendNotification = async (receiver: string, meta: Meta) => {
|
|||||||
*/
|
*/
|
||||||
export const getProfileUsername = (
|
export const getProfileUsername = (
|
||||||
npub: `npub1${string}` | string,
|
npub: `npub1${string}` | string,
|
||||||
profile?: ProfileMetadata
|
profile?: ProfileMetadata // todo: use NDKUserProfile
|
||||||
) =>
|
) =>
|
||||||
truncate(profile?.display_name || profile?.name || hexToNpub(npub), {
|
truncate(profile?.display_name || profile?.name || hexToNpub(npub), {
|
||||||
length: 16
|
length: 16
|
||||||
|
Loading…
Reference in New Issue
Block a user