Add KeygenStep, only show ExtensionStep when window.nostr is present

This commit is contained in:
Alex Gleason 2024-02-18 22:55:05 -06:00
parent f2bfa6e2f6
commit 49bde675c3
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
7 changed files with 85 additions and 6 deletions

View File

@ -77,10 +77,11 @@ export class NKeyStorage implements ReadonlyMap<string, NostrSigner> {
return this.#keypairs.has(pubkey);
}
add(secretKey: Uint8Array): void {
add(secretKey: Uint8Array): NostrSigner {
const pubkey = getPublicKey(secretKey);
this.#keypairs.set(pubkey, secretKey);
this.#syncStorage();
return this.get(pubkey)!;
}
*entries(): IterableIterator<[string, NostrSigner]> {

View File

@ -10,7 +10,7 @@ interface IEmojiGraphic {
const EmojiGraphic: React.FC<IEmojiGraphic> = ({ emoji }) => {
return (
<div className='flex items-center justify-center'>
<div className='my-6 rounded-full bg-gray-100 p-8 dark:bg-gray-800'>
<div className='rounded-full bg-gray-100 p-8 dark:bg-gray-800'>
<Emoji className='h-24 w-24' emoji={emoji} />
</div>
</div>

View File

@ -1,9 +1,11 @@
import { NostrSigner } from 'nspec';
import React, { useState } from 'react';
import AccountStep from './steps/account-step';
import ExtensionStep from './steps/extension-step';
import IdentityStep from './steps/identity-step';
import KeyStep from './steps/key-step';
import KeygenStep from './steps/keygen-step';
import RegisterStep from './steps/register-step';
interface INostrSigninModal {
@ -11,8 +13,9 @@ interface INostrSigninModal {
}
const NostrSigninModal: React.FC<INostrSigninModal> = ({ onClose }) => {
const [step, setStep] = useState(0);
const [step, setStep] = useState(window.nostr ? 0 : 1);
const [, setSigner] = useState<NostrSigner | undefined>();
const [accountId, setAccountId] = useState<string | undefined>();
const handleClose = () => onClose('NOSTR_SIGNIN');
@ -28,6 +31,8 @@ const NostrSigninModal: React.FC<INostrSigninModal> = ({ onClose }) => {
return <AccountStep accountId={accountId!} setStep={setStep} onClose={handleClose} />;
case 4:
return <RegisterStep onClose={handleClose} />;
case 5:
return <KeygenStep setSigner={setSigner} setStep={setStep} onClose={handleClose} />;
default:
return null;
}

View File

@ -53,7 +53,9 @@ const IdentityStep: React.FC<IIdentityStep> = ({ setAccountId, setStep, onClose
<Modal title={<FormattedMessage id='nostr_signin.identity.title' defaultMessage='Who are you?' />} onClose={onClose}>
<Form>
<Stack className='mt-3' space={3}>
<NostrExtensionIndicator />
<div className='my-3'>
<NostrExtensionIndicator />
</div>
<EmojiGraphic emoji='🕵️' />

View File

@ -14,13 +14,13 @@ interface IKeyStep {
const KeyStep: React.FC<IKeyStep> = ({ setStep, onClose }) => {
return (
<Modal title={<FormattedMessage id='nostr_signin.key.title' defaultMessage='You need a key to continue' />} onClose={onClose}>
<Stack className='my-3' space={6} justifyContent='center'>
<Stack className='my-3' space={6}>
<NostrExtensionIndicator />
<EmojiGraphic emoji='🔑' />
<Stack space={3} alignItems='center'>
<Button theme='accent' size='lg'>
<Button theme='accent' size='lg' onClick={() => setStep(5)}>
Generate key
</Button>

View File

@ -0,0 +1,62 @@
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
import { NostrSigner } from 'nspec';
import React, { useMemo, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Button, Stack, Modal } from 'soapbox/components/ui';
import { NKeys } from 'soapbox/features/nostr/keys';
import { useInstance } from 'soapbox/hooks';
import { download } from 'soapbox/utils/download';
import { slugify } from 'soapbox/utils/input';
import EmojiGraphic from '../components/emoji-graphic';
interface IKeygenStep {
setSigner(signer: NostrSigner): void;
setStep(step: number): void;
onClose(): void;
}
const KeygenStep: React.FC<IKeygenStep> = ({ setSigner, setStep, onClose }) => {
const instance = useInstance();
const secretKey = useMemo(() => generateSecretKey(), []);
const pubkey = useMemo(() => getPublicKey(secretKey), [secretKey]);
const nsec = useMemo(() => nip19.nsecEncode(secretKey), [secretKey]);
const npub = useMemo(() => nip19.npubEncode(pubkey), [pubkey]);
const [downloaded, setDownloaded] = useState(false);
const handleDownload = () => {
download(nsec, `${slugify(instance.title)}-${npub.slice(5, 9)}.nsec`);
setDownloaded(true);
};
const handleNext = () => {
const signer = NKeys.add(secretKey);
setSigner(signer);
};
return (
<Modal title={<FormattedMessage id='nostr_signin.keygen.title' defaultMessage='Your new key' />} onClose={onClose}>
<Stack className='my-3' space={6}>
<EmojiGraphic emoji='🔑' />
<Stack alignItems='center'>
<Button theme='primary' size='lg' icon={require('@tabler/icons/download.svg')} onClick={handleDownload}>
Download key
</Button>
</Stack>
<Stack space={3} alignItems='end'>
<Button theme='accent' disabled={!downloaded} size='lg' onClick={handleNext}>
Next
</Button>
</Stack>
</Stack>
</Modal>
);
};
export default KeygenStep;

View File

@ -8,6 +8,15 @@ const normalizeUsername = (username: string): string => {
}
};
function slugify(text: string): string {
return text
.trim()
.toLowerCase()
.replace(/[^\w]/g, '-') // replace non-word characters with a hyphen
.replace(/-+/g, '-'); // replace multiple hyphens with a single hyphen
}
export {
normalizeUsername,
slugify,
};