Landing page - new design implementation #122
@ -1,383 +1,3 @@
|
|||||||
import { Box, Button, TextField } from '@mui/material'
|
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { useDispatch } from 'react-redux'
|
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
|
||||||
import {
|
|
||||||
AuthController,
|
|
||||||
MetadataController,
|
|
||||||
NostrController
|
|
||||||
} from '../../controllers'
|
|
||||||
import {
|
|
||||||
updateKeyPair,
|
|
||||||
updateLoginMethod,
|
|
||||||
updateNsecbunkerPubkey,
|
|
||||||
updateNsecbunkerRelays
|
|
||||||
} from '../../store/actions'
|
|
||||||
import { LoginMethods } from '../../store/auth/types'
|
|
||||||
import { Dispatch } from '../../store/store'
|
|
||||||
import { npubToHex, queryNip05 } from '../../utils'
|
|
||||||
import styles from './style.module.scss'
|
|
||||||
import { hexToBytes } from '@noble/hashes/utils'
|
|
||||||
import { NIP05_REGEX } from '../../constants'
|
|
||||||
import { appPublicRoutes } from '../../routes'
|
|
||||||
|
|
||||||
export const Login = () => {
|
export const Login = () => {
|
||||||
const [searchParams] = useSearchParams()
|
return <>Login</>
|
||||||
|
|
||||||
const dispatch: Dispatch = useDispatch()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const authController = new AuthController()
|
|
||||||
const metadataController = new MetadataController()
|
|
||||||
const nostrController = NostrController.getInstance()
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
||||||
const [inputValue, setInputValue] = useState('')
|
|
||||||
const [authUrl, setAuthUrl] = useState<string>()
|
|
||||||
|
|
||||||
const [isNostrExtensionAvailable, setIsNostrExtensionAvailable] =
|
|
||||||
useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsNostrExtensionAvailable(!!window.nostr)
|
|
||||||
}, 500)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call login function when enter is pressed
|
|
||||||
*/
|
|
||||||
const handleInputKeyDown = (event: any) => {
|
|
||||||
if (event.code === 'Enter' || event.code === 'NumpadEnter') {
|
|
||||||
event.preventDefault()
|
|
||||||
login()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const navigateAfterLogin = (path: string) => {
|
|
||||||
const callbackPath = searchParams.get('callbackPath')
|
|
||||||
|
|
||||||
if (callbackPath) {
|
|
||||||
// base64 decoded path
|
|
||||||
const path = atob(callbackPath)
|
|
||||||
navigate(path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginWithExtension = async () => {
|
|
||||||
setIsLoading(true)
|
|
||||||
setLoadingSpinnerDesc('Capturing pubkey from nostr extension')
|
|
||||||
|
|
||||||
nostrController
|
|
||||||
.capturePublicKey()
|
|
||||||
.then(async (pubkey) => {
|
|
||||||
dispatch(updateLoginMethod(LoginMethods.extension))
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
|
||||||
const redirectPath =
|
|
||||||
await authController.authAndGetMetadataAndRelaysMap(pubkey)
|
|
||||||
|
|
||||||
if (redirectPath) navigateAfterLogin(redirectPath)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error('Error capturing public key from nostr extension: ' + err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Login with NSEC or HEX private key
|
|
||||||
* @param privateKey in HEX format
|
|
||||||
*/
|
|
||||||
const loginWithNsec = async (privateKey?: Uint8Array) => {
|
|
||||||
let nsec = ''
|
|
||||||
|
|
||||||
if (privateKey) {
|
|
||||||
nsec = nip19.nsecEncode(privateKey)
|
|
||||||
} else {
|
|
||||||
nsec = inputValue
|
|
||||||
|
|
||||||
try {
|
|
||||||
privateKey = nip19.decode(nsec).data as Uint8Array
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(`Error decoding the nsec. ${err}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!privateKey) {
|
|
||||||
toast.error(
|
|
||||||
'Snap, we failed to convert the private key you provided. Please make sure key is valid.'
|
|
||||||
)
|
|
||||||
setIsLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const publickey = getPublicKey(privateKey)
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
updateKeyPair({
|
|
||||||
private: nsec,
|
|
||||||
public: publickey
|
|
||||||
})
|
|
||||||
)
|
|
||||||
dispatch(updateLoginMethod(LoginMethods.privateKey))
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
|
||||||
|
|
||||||
const redirectPath = await authController
|
|
||||||
.authAndGetMetadataAndRelaysMap(publickey)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error('Error occurred in authentication: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (redirectPath) navigateAfterLogin(redirectPath)
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginWithNsecBunker = async () => {
|
|
||||||
let relays: string[] | undefined
|
|
||||||
let pubkey: string | undefined
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
const displayError = (message: string) => {
|
|
||||||
toast.error(message)
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputValue.match(NIP05_REGEX)) {
|
|
||||||
const nip05Profile = await queryNip05(inputValue).catch((err) => {
|
|
||||||
toast.error('An error occurred while querying nip05 profile: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (nip05Profile) {
|
|
||||||
pubkey = nip05Profile.pubkey
|
|
||||||
relays = nip05Profile.relays
|
|
||||||
}
|
|
||||||
} else if (inputValue.startsWith('npub')) {
|
|
||||||
pubkey = nip19.decode(inputValue).data as string
|
|
||||||
const metadataEvent = await metadataController
|
|
||||||
.findMetadata(pubkey)
|
|
||||||
.catch(() => {
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!metadataEvent) {
|
|
||||||
return displayError('metadata not found!')
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadataContent =
|
|
||||||
metadataController.extractProfileMetadataContent(metadataEvent)
|
|
||||||
|
|
||||||
if (!metadataContent?.nip05) {
|
|
||||||
return displayError('nip05 not present in metadata')
|
|
||||||
}
|
|
||||||
|
|
||||||
const nip05Profile = await queryNip05(inputValue).catch((err) => {
|
|
||||||
toast.error('An error occurred while querying nip05 profile: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (nip05Profile) {
|
|
||||||
if (nip05Profile.pubkey !== pubkey) {
|
|
||||||
return displayError(
|
|
||||||
'pubkey in nip05 does not match with provided npub'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
relays = nip05Profile.relays
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relays || relays.length === 0) {
|
|
||||||
return displayError('No relay found for nsecbunker')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pubkey) {
|
|
||||||
return displayError('pubkey not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Initializing nsecBunker')
|
|
||||||
await nostrController.nsecBunkerInit(relays)
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Creating nsecbunker singer')
|
|
||||||
await nostrController
|
|
||||||
.createNsecBunkerSigner(pubkey)
|
|
||||||
.then(async (signer) => {
|
|
||||||
signer.on('authUrl', (url: string) => {
|
|
||||||
setAuthUrl(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(updateLoginMethod(LoginMethods.nsecBunker))
|
|
||||||
dispatch(updateNsecbunkerPubkey(pubkey))
|
|
||||||
dispatch(updateNsecbunkerRelays(relays))
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
|
||||||
|
|
||||||
const redirectPath = await authController
|
|
||||||
.authAndGetMetadataAndRelaysMap(pubkey!)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error('Error occurred in authentication: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (redirectPath) navigateAfterLogin(redirectPath)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(
|
|
||||||
'An error occurred while creating nsecbunker signer: ' + err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginWithBunkerConnectionString = async () => {
|
|
||||||
// Extract the key
|
|
||||||
const keyStartIndex = inputValue.indexOf('bunker://') + 'bunker://'.length
|
|
||||||
const keyEndIndex = inputValue.indexOf('?relay=')
|
|
||||||
const key = inputValue.substring(keyStartIndex, keyEndIndex)
|
|
||||||
|
|
||||||
const pubkey = npubToHex(key)
|
|
||||||
|
|
||||||
if (!pubkey) {
|
|
||||||
toast.error('Invalid pubkey in bunker connection string.')
|
|
||||||
setIsLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the relay value
|
|
||||||
const relayIndex = inputValue.indexOf('relay=')
|
|
||||||
const relay = inputValue.substring(
|
|
||||||
relayIndex + 'relay='.length,
|
|
||||||
inputValue.length
|
|
||||||
)
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setLoadingSpinnerDesc('Initializing bunker NDK')
|
|
||||||
|
|
||||||
await nostrController.nsecBunkerInit([relay])
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Creating remote signer')
|
|
||||||
await nostrController
|
|
||||||
.createNsecBunkerSigner(pubkey)
|
|
||||||
.then(async (signer) => {
|
|
||||||
signer.on('authUrl', (url: string) => {
|
|
||||||
setAuthUrl(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(updateLoginMethod(LoginMethods.nsecBunker))
|
|
||||||
dispatch(updateNsecbunkerPubkey(pubkey))
|
|
||||||
dispatch(updateNsecbunkerRelays([relay]))
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
|
||||||
|
|
||||||
const redirectPath = await authController
|
|
||||||
.authAndGetMetadataAndRelaysMap(pubkey!)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error('Error occurred in authentication: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (redirectPath) navigateAfterLogin(redirectPath)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(
|
|
||||||
'An error occurred while creating nsecbunker signer: ' + err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const login = () => {
|
|
||||||
if (inputValue.startsWith('bunker://')) {
|
|
||||||
return loginWithBunkerConnectionString()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputValue.startsWith('nsec')) {
|
|
||||||
return loginWithNsec()
|
|
||||||
}
|
|
||||||
if (inputValue.startsWith('npub')) {
|
|
||||||
return loginWithNsecBunker()
|
|
||||||
}
|
|
||||||
if (inputValue.match(NIP05_REGEX)) {
|
|
||||||
return loginWithNsecBunker()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if maybe hex nsec
|
|
||||||
try {
|
|
||||||
const privateKey = hexToBytes(inputValue)
|
|
||||||
const publickey = getPublicKey(privateKey)
|
|
||||||
|
|
||||||
if (publickey) return loginWithNsec(privateKey)
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('err', err)
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.error(
|
|
||||||
'Invalid format, please use: private key (hex), nsec..., bunker:// or nip05 format.'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authUrl) {
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
title="Nsecbunker auth"
|
|
||||||
src={authUrl}
|
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<div className={styles.loginPage}>
|
|
||||||
<TextField
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
label="nip05 login / nip46 bunker string"
|
|
||||||
value={inputValue}
|
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
|
||||||
sx={{ width: '100%', mt: 2 }}
|
|
||||||
/>
|
|
||||||
{isNostrExtensionAvailable && (
|
|
||||||
<Button onClick={loginWithExtension} variant="text">
|
|
||||||
Login with extension
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<Button disabled={!inputValue} onClick={login} variant="contained">
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,382 @@
|
|||||||
|
import { Box, Button, TextField } from '@mui/material'
|
||||||
|
import { getPublicKey, nip19 } from 'nostr-tools'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
|
import {
|
||||||
|
AuthController,
|
||||||
|
MetadataController,
|
||||||
|
NostrController
|
||||||
|
} from '../../controllers'
|
||||||
|
import {
|
||||||
|
updateKeyPair,
|
||||||
|
updateLoginMethod,
|
||||||
|
updateNsecbunkerPubkey,
|
||||||
|
updateNsecbunkerRelays
|
||||||
|
} from '../../store/actions'
|
||||||
|
import { LoginMethods } from '../../store/auth/types'
|
||||||
|
import { Dispatch } from '../../store/store'
|
||||||
|
import { npubToHex, queryNip05 } from '../../utils'
|
||||||
|
import styles from './style.module.scss'
|
||||||
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
|
import { NIP05_REGEX } from '../../constants'
|
||||||
|
|
||||||
export const Nostr = () => {
|
export const Nostr = () => {
|
||||||
return <>Nostr</>
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
|
const dispatch: Dispatch = useDispatch()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const authController = new AuthController()
|
||||||
|
const metadataController = new MetadataController()
|
||||||
|
const nostrController = NostrController.getInstance()
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
|
const [inputValue, setInputValue] = useState('')
|
||||||
|
const [authUrl, setAuthUrl] = useState<string>()
|
||||||
|
|
||||||
|
const [isNostrExtensionAvailable, setIsNostrExtensionAvailable] =
|
||||||
|
useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsNostrExtensionAvailable(!!window.nostr)
|
||||||
|
}, 500)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call login function when enter is pressed
|
||||||
|
*/
|
||||||
|
const handleInputKeyDown = (event: any) => {
|
||||||
|
if (event.code === 'Enter' || event.code === 'NumpadEnter') {
|
||||||
|
event.preventDefault()
|
||||||
|
login()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateAfterLogin = (path: string) => {
|
||||||
|
const callbackPath = searchParams.get('callbackPath')
|
||||||
|
|
||||||
|
if (callbackPath) {
|
||||||
|
// base64 decoded path
|
||||||
|
const path = atob(callbackPath)
|
||||||
|
navigate(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginWithExtension = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
setLoadingSpinnerDesc('Capturing pubkey from nostr extension')
|
||||||
|
|
||||||
|
nostrController
|
||||||
|
.capturePublicKey()
|
||||||
|
.then(async (pubkey) => {
|
||||||
|
dispatch(updateLoginMethod(LoginMethods.extension))
|
||||||
|
|
||||||
|
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
||||||
|
const redirectPath =
|
||||||
|
await authController.authAndGetMetadataAndRelaysMap(pubkey)
|
||||||
|
|
||||||
|
if (redirectPath) navigateAfterLogin(redirectPath)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Error capturing public key from nostr extension: ' + err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
setLoadingSpinnerDesc('')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login with NSEC or HEX private key
|
||||||
|
* @param privateKey in HEX format
|
||||||
|
*/
|
||||||
|
const loginWithNsec = async (privateKey?: Uint8Array) => {
|
||||||
|
let nsec = ''
|
||||||
|
|
||||||
|
if (privateKey) {
|
||||||
|
nsec = nip19.nsecEncode(privateKey)
|
||||||
|
} else {
|
||||||
|
nsec = inputValue
|
||||||
|
|
||||||
|
try {
|
||||||
|
privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(`Error decoding the nsec. ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!privateKey) {
|
||||||
|
toast.error(
|
||||||
|
'Snap, we failed to convert the private key you provided. Please make sure key is valid.'
|
||||||
|
)
|
||||||
|
setIsLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const publickey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateKeyPair({
|
||||||
|
private: nsec,
|
||||||
|
public: publickey
|
||||||
|
})
|
||||||
|
)
|
||||||
|
dispatch(updateLoginMethod(LoginMethods.privateKey))
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
||||||
|
|
||||||
|
const redirectPath = await authController
|
||||||
|
.authAndGetMetadataAndRelaysMap(publickey)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Error occurred in authentication: ' + err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (redirectPath) navigateAfterLogin(redirectPath)
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
|
setLoadingSpinnerDesc('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginWithNsecBunker = async () => {
|
||||||
|
let relays: string[] | undefined
|
||||||
|
let pubkey: string | undefined
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
const displayError = (message: string) => {
|
||||||
|
toast.error(message)
|
||||||
|
setIsLoading(false)
|
||||||
|
setLoadingSpinnerDesc('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputValue.match(NIP05_REGEX)) {
|
||||||
|
const nip05Profile = await queryNip05(inputValue).catch((err) => {
|
||||||
|
toast.error('An error occurred while querying nip05 profile: ' + err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (nip05Profile) {
|
||||||
|
pubkey = nip05Profile.pubkey
|
||||||
|
relays = nip05Profile.relays
|
||||||
|
}
|
||||||
|
} else if (inputValue.startsWith('npub')) {
|
||||||
|
pubkey = nip19.decode(inputValue).data as string
|
||||||
|
const metadataEvent = await metadataController
|
||||||
|
.findMetadata(pubkey)
|
||||||
|
.catch(() => {
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!metadataEvent) {
|
||||||
|
return displayError('metadata not found!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadataContent =
|
||||||
|
metadataController.extractProfileMetadataContent(metadataEvent)
|
||||||
|
|
||||||
|
if (!metadataContent?.nip05) {
|
||||||
|
return displayError('nip05 not present in metadata')
|
||||||
|
}
|
||||||
|
|
||||||
|
const nip05Profile = await queryNip05(inputValue).catch((err) => {
|
||||||
|
toast.error('An error occurred while querying nip05 profile: ' + err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (nip05Profile) {
|
||||||
|
if (nip05Profile.pubkey !== pubkey) {
|
||||||
|
return displayError(
|
||||||
|
'pubkey in nip05 does not match with provided npub'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
relays = nip05Profile.relays
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relays || relays.length === 0) {
|
||||||
|
return displayError('No relay found for nsecbunker')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pubkey) {
|
||||||
|
return displayError('pubkey not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingSpinnerDesc('Initializing nsecBunker')
|
||||||
|
await nostrController.nsecBunkerInit(relays)
|
||||||
|
|
||||||
|
setLoadingSpinnerDesc('Creating nsecbunker singer')
|
||||||
|
await nostrController
|
||||||
|
.createNsecBunkerSigner(pubkey)
|
||||||
|
.then(async (signer) => {
|
||||||
|
signer.on('authUrl', (url: string) => {
|
||||||
|
setAuthUrl(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(updateLoginMethod(LoginMethods.nsecBunker))
|
||||||
|
dispatch(updateNsecbunkerPubkey(pubkey))
|
||||||
|
dispatch(updateNsecbunkerRelays(relays))
|
||||||
|
|
||||||
|
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
||||||
|
|
||||||
|
const redirectPath = await authController
|
||||||
|
.authAndGetMetadataAndRelaysMap(pubkey!)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Error occurred in authentication: ' + err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (redirectPath) navigateAfterLogin(redirectPath)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(
|
||||||
|
'An error occurred while creating nsecbunker signer: ' + err
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
setLoadingSpinnerDesc('')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginWithBunkerConnectionString = async () => {
|
||||||
|
// Extract the key
|
||||||
|
const keyStartIndex = inputValue.indexOf('bunker://') + 'bunker://'.length
|
||||||
|
const keyEndIndex = inputValue.indexOf('?relay=')
|
||||||
|
const key = inputValue.substring(keyStartIndex, keyEndIndex)
|
||||||
|
|
||||||
|
const pubkey = npubToHex(key)
|
||||||
|
|
||||||
|
if (!pubkey) {
|
||||||
|
toast.error('Invalid pubkey in bunker connection string.')
|
||||||
|
setIsLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the relay value
|
||||||
|
const relayIndex = inputValue.indexOf('relay=')
|
||||||
|
const relay = inputValue.substring(
|
||||||
|
relayIndex + 'relay='.length,
|
||||||
|
inputValue.length
|
||||||
|
)
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
setLoadingSpinnerDesc('Initializing bunker NDK')
|
||||||
|
|
||||||
|
await nostrController.nsecBunkerInit([relay])
|
||||||
|
|
||||||
|
setLoadingSpinnerDesc('Creating remote signer')
|
||||||
|
await nostrController
|
||||||
|
.createNsecBunkerSigner(pubkey)
|
||||||
|
.then(async (signer) => {
|
||||||
|
signer.on('authUrl', (url: string) => {
|
||||||
|
setAuthUrl(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(updateLoginMethod(LoginMethods.nsecBunker))
|
||||||
|
dispatch(updateNsecbunkerPubkey(pubkey))
|
||||||
|
dispatch(updateNsecbunkerRelays([relay]))
|
||||||
|
|
||||||
|
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
||||||
|
|
||||||
|
const redirectPath = await authController
|
||||||
|
.authAndGetMetadataAndRelaysMap(pubkey!)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Error occurred in authentication: ' + err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (redirectPath) navigateAfterLogin(redirectPath)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(
|
||||||
|
'An error occurred while creating nsecbunker signer: ' + err
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
setLoadingSpinnerDesc('')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const login = () => {
|
||||||
|
if (inputValue.startsWith('bunker://')) {
|
||||||
|
return loginWithBunkerConnectionString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputValue.startsWith('nsec')) {
|
||||||
|
return loginWithNsec()
|
||||||
|
}
|
||||||
|
if (inputValue.startsWith('npub')) {
|
||||||
|
return loginWithNsecBunker()
|
||||||
|
}
|
||||||
|
if (inputValue.match(NIP05_REGEX)) {
|
||||||
|
return loginWithNsecBunker()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if maybe hex nsec
|
||||||
|
try {
|
||||||
|
const privateKey = hexToBytes(inputValue)
|
||||||
|
const publickey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
if (publickey) return loginWithNsec(privateKey)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('err', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error(
|
||||||
|
'Invalid format, please use: private key (hex), nsec..., bunker:// or nip05 format.'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authUrl) {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
title="Nsecbunker auth"
|
||||||
|
src={authUrl}
|
||||||
|
width="100%"
|
||||||
|
height="500px"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<div className={styles.loginPage}>
|
||||||
|
<TextField
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
label="nip05 login / nip46 bunker string"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
sx={{ width: '100%', mt: 2 }}
|
||||||
|
/>
|
||||||
|
{isNostrExtensionAvailable && (
|
||||||
|
<Button onClick={loginWithExtension} variant="text">
|
||||||
|
Login with extension
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Button disabled={!inputValue} onClick={login} variant="contained">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user