import { useState, useCallback, useEffect, useRef } from 'react' import { gql, useMutation } from '@apollo/client' import { signIn } from 'next-auth/react' import Col from 'react-bootstrap/Col' import Row from 'react-bootstrap/Row' import { useRouter } from 'next/router' import AccordianItem from './accordian-item' import BackIcon from '@/svgs/arrow-left-line.svg' import Nostr from '@/lib/nostr' import { NDKNip46Signer } from '@nostr-dev-kit/ndk' import { useToast } from '@/components/toast' import { Button, Container } from 'react-bootstrap' import { Form, Input, SubmitButton } from '@/components/form' import Moon from '@/svgs/moon-fill.svg' import styles from './lightning-auth.module.css' const sanitizeURL = (s) => { try { const url = new URL(s) if (url.protocol !== 'https:' && url.protocol !== 'http:') throw new Error('invalid protocol') return url.href } catch (e) { return null } } function NostrError ({ message }) { return ( <>

error

{message}
) } export function NostrAuth ({ text, callbackUrl, multiAuth }) { const [status, setStatus] = useState({ msg: '', error: false, loading: false, title: undefined, button: undefined }) const [suggestion, setSuggestion] = useState(null) const suggestionTimeout = useRef(null) const toaster = useToast() const challengeResolver = useCallback(async (challenge) => { const challengeUrl = sanitizeURL(challenge) if (challengeUrl) { setStatus({ title: 'Waiting for confirmation', msg: 'Please confirm this action on your remote signer', error: false, loading: true, button: { label: 'open signer', action: () => { window.open(challengeUrl, '_blank') } } }) } else { setStatus({ title: 'Waiting for confirmation', msg: challenge, error: false, loading: true }) } }, []) // create auth challenge const [createAuth] = useMutation(gql` mutation createAuth { createAuth { k1 } }`, { // don't cache this mutation fetchPolicy: 'no-cache' }) // print an error message const setError = useCallback((e) => { console.error(e) toaster.danger(e.message || e.toString()) setStatus({ msg: e.message || e.toString(), error: true, loading: false }) }, []) const clearSuggestionTimer = () => { if (suggestionTimeout.current) clearTimeout(suggestionTimeout.current) } const setSuggestionWithTimer = (msg) => { clearSuggestionTimer() suggestionTimeout.current = setTimeout(() => { setSuggestion(msg) }, 10_000) } useEffect(() => { return () => { clearSuggestionTimer() } }, []) // authorize user const auth = useCallback(async (nip46token) => { setStatus({ msg: 'Waiting for authorization', error: false, loading: true }) try { const { data, error } = await createAuth() if (error) throw error const k1 = data?.createAuth.k1 if (!k1) throw new Error('Error generating challenge') // should never happen const useExtension = !nip46token const signer = Nostr.getSigner({ nip46token, supportNip07: useExtension }) if (!signer && useExtension) throw new Error('No extension found') if (signer instanceof NDKNip46Signer) { signer.once('authUrl', challengeResolver) } setSuggestionWithTimer('Having trouble? Make sure you used a fresh token or valid NIP-05 address') await signer.blockUntilReady() clearSuggestionTimer() setStatus({ msg: 'Signing in', error: false, loading: true }) const signedEvent = await Nostr.sign({ kind: 27235, created_at: Math.floor(Date.now() / 1000), tags: [ ['challenge', k1], ['u', process.env.NEXT_PUBLIC_URL], ['method', 'GET'] ], content: 'Stacker News Authentication' }, { signer }) await signIn('nostr', { event: JSON.stringify(signedEvent), callbackUrl, multiAuth }) } catch (e) { setError(e) } finally { clearSuggestionTimer() } }, []) return ( <> {status.error && } {status.loading ? ( <>
{status.msg}
{status.button && ( )} {suggestion && (
{suggestion}
)} ) : ( <>
{ if (!values.token) { setError(new Error('Token or NIP-05 address is required')) } else { auth(values.token) } }} >
{text || 'Login'} with token or NIP-05
or
)} ) } function NostrExplainer ({ text, children }) { const router = useRouter() return (
router.back()}>

{text || 'Login'} with Nostr

  • Nsec.app
    • available for: chrome, firefox, and safari
  • nsecBunker
    • available as: SaaS or self-hosted
} />
  • Alby
    • available for: chrome, firefox, and safari
  • Flamingo
    • available for: chrome
  • nos2x
    • available for: chrome
  • nos2x-fox
    • available for: firefox
  • horse
    • available for: chrome
    • supports hardware signing
} /> {children}
) } export function NostrAuthWithExplainer ({ text, callbackUrl, multiAuth }) { return ( ) }