2024-10-04 11:37:20 +02:00
|
|
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
|
|
|
|
2024-07-29 17:12:47 +02:00
|
|
|
import { Button, Divider, TextField } from '@mui/material'
|
2024-07-26 15:45:13 +02:00
|
|
|
import { getPublicKey, nip19 } from 'nostr-tools'
|
|
|
|
import { useEffect, useState } from 'react'
|
2024-10-08 17:08:43 +02:00
|
|
|
import { useAppDispatch } from '../../hooks/store'
|
2024-07-26 15:45:13 +02:00
|
|
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
|
|
|
import { toast } from 'react-toastify'
|
|
|
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
2024-10-05 14:56:34 +02:00
|
|
|
import { AuthController } from '../../controllers'
|
|
|
|
import { updateKeyPair, updateLoginMethod } from '../../store/actions'
|
|
|
|
import { LoginMethod } from '../../store/auth/types'
|
2024-07-26 15:45:13 +02:00
|
|
|
import { hexToBytes } from '@noble/hashes/utils'
|
|
|
|
|
2024-07-30 12:23:38 +02:00
|
|
|
import styles from './styles.module.scss'
|
2024-07-29 17:12:47 +02:00
|
|
|
|
2024-07-26 15:38:44 +02:00
|
|
|
export const Nostr = () => {
|
2024-07-26 15:45:13 +02:00
|
|
|
const [searchParams] = useSearchParams()
|
|
|
|
|
2024-10-08 17:08:43 +02:00
|
|
|
const dispatch = useAppDispatch()
|
2024-07-26 15:45:13 +02:00
|
|
|
const navigate = useNavigate()
|
|
|
|
|
|
|
|
const authController = new AuthController()
|
|
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
|
|
|
const [inputValue, setInputValue] = useState('')
|
2024-10-05 14:56:34 +02:00
|
|
|
|
|
|
|
const navigateAfterLogin = (path: string) => {
|
|
|
|
const callbackPath = searchParams.get('callbackPath')
|
|
|
|
|
|
|
|
if (callbackPath) {
|
|
|
|
// base64 decoded path
|
|
|
|
const path = atob(callbackPath)
|
|
|
|
navigate(path)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
navigate(path)
|
|
|
|
}
|
2024-07-26 15:45:13 +02:00
|
|
|
|
|
|
|
const [isNostrExtensionAvailable, setIsNostrExtensionAvailable] =
|
|
|
|
useState(false)
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setTimeout(() => {
|
|
|
|
setIsNostrExtensionAvailable(!!window.nostr)
|
|
|
|
}, 500)
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Call login function when enter is pressed
|
|
|
|
*/
|
2024-08-05 09:49:56 +02:00
|
|
|
const handleInputKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
2024-07-26 15:45:13 +02:00
|
|
|
if (event.code === 'Enter' || event.code === 'NumpadEnter') {
|
|
|
|
event.preventDefault()
|
|
|
|
login()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
})
|
|
|
|
)
|
2024-10-05 14:56:34 +02:00
|
|
|
dispatch(updateLoginMethod(LoginMethod.privateKey))
|
2024-07-26 15:45:13 +02:00
|
|
|
|
|
|
|
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 login = () => {
|
|
|
|
if (inputValue.startsWith('nsec')) {
|
|
|
|
return loginWithNsec()
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2024-10-05 14:56:34 +02:00
|
|
|
toast.error('Invalid format, please use: private key (hex or nsec)')
|
2024-07-26 15:45:13 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2024-10-05 14:56:34 +02:00
|
|
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
2024-07-29 17:12:47 +02:00
|
|
|
{isNostrExtensionAvailable && (
|
|
|
|
<>
|
2024-07-30 12:23:38 +02:00
|
|
|
<label className={styles.label} htmlFor="extension-login">
|
2024-10-05 14:56:34 +02:00
|
|
|
Login by using a{' '}
|
|
|
|
<a
|
|
|
|
rel="noopener"
|
|
|
|
href="https://github.com/nostrband/nostr-login"
|
|
|
|
target="_blank"
|
|
|
|
>
|
|
|
|
nostr-login
|
|
|
|
</a>
|
2024-07-29 17:12:47 +02:00
|
|
|
</label>
|
|
|
|
<Button
|
2024-10-05 14:56:34 +02:00
|
|
|
id="nostr-login"
|
2024-07-29 17:12:47 +02:00
|
|
|
variant="contained"
|
2024-10-05 14:56:34 +02:00
|
|
|
onClick={() => {
|
2024-10-08 13:45:58 +02:00
|
|
|
launchNostrLoginDialog()
|
2024-10-05 14:56:34 +02:00
|
|
|
}}
|
2024-07-29 17:12:47 +02:00
|
|
|
>
|
2024-10-05 14:56:34 +02:00
|
|
|
Nostr Login
|
2024-07-29 17:12:47 +02:00
|
|
|
</Button>
|
|
|
|
<Divider
|
|
|
|
sx={{
|
|
|
|
fontSize: '16px'
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
or
|
|
|
|
</Divider>
|
|
|
|
</>
|
|
|
|
)}
|
2024-10-05 14:56:34 +02:00
|
|
|
<form autoComplete="off">
|
|
|
|
<TextField
|
|
|
|
onKeyDown={handleInputKeyDown}
|
|
|
|
label="Private key (Not recommended)"
|
|
|
|
type="password"
|
|
|
|
autoComplete="off"
|
|
|
|
value={inputValue}
|
|
|
|
onChange={(e) => setInputValue(e.target.value)}
|
|
|
|
fullWidth
|
|
|
|
margin="dense"
|
|
|
|
/>
|
|
|
|
</form>
|
2024-07-29 17:12:47 +02:00
|
|
|
<Button
|
|
|
|
disabled={!inputValue}
|
|
|
|
onClick={login}
|
|
|
|
variant="contained"
|
|
|
|
fullWidth
|
|
|
|
>
|
|
|
|
Login
|
|
|
|
</Button>
|
2024-07-26 15:45:13 +02:00
|
|
|
</>
|
|
|
|
)
|
2024-07-26 15:38:44 +02:00
|
|
|
}
|