11 changed files with 196 additions and 30 deletions
@ -1,5 +1,5 @@ |
|||||||
export const StorageKey = { |
export const StorageKey = { |
||||||
THEME_SETTING: 'themeSetting', |
THEME_SETTING: 'themeSetting', |
||||||
RELAY_GROUPS: 'relayGroups', |
RELAY_GROUPS: 'relayGroups', |
||||||
ACCOUNT: 'account' |
ACCOUNTS: 'accounts' |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,47 @@ |
|||||||
|
import { Button } from '@renderer/components/ui/button' |
||||||
|
import { Input } from '@renderer/components/ui/input' |
||||||
|
import { useNostr } from '@renderer/providers/NostrProvider' |
||||||
|
import { Loader } from 'lucide-react' |
||||||
|
import { useState } from 'react' |
||||||
|
import { useTranslation } from 'react-i18next' |
||||||
|
|
||||||
|
export default function BunkerLogin({ onLoginSuccess }: { onLoginSuccess: () => void }) { |
||||||
|
const { t } = useTranslation() |
||||||
|
const { bunkerLogin } = useNostr() |
||||||
|
const [pending, setPending] = useState(false) |
||||||
|
const [bunkerInput, setBunkerInput] = useState('') |
||||||
|
const [errMsg, setErrMsg] = useState<string | null>(null) |
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||||
|
setBunkerInput(e.target.value) |
||||||
|
setErrMsg(null) |
||||||
|
} |
||||||
|
|
||||||
|
const handleLogin = () => { |
||||||
|
if (bunkerInput === '') return |
||||||
|
|
||||||
|
setPending(true) |
||||||
|
bunkerLogin(bunkerInput) |
||||||
|
.then(() => onLoginSuccess()) |
||||||
|
.catch((err) => setErrMsg(err.message)) |
||||||
|
.finally(() => setPending(false)) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="space-y-1"> |
||||||
|
<Input |
||||||
|
placeholder="bunker://..." |
||||||
|
value={bunkerInput} |
||||||
|
onChange={handleInputChange} |
||||||
|
className={errMsg ? 'border-destructive' : ''} |
||||||
|
/> |
||||||
|
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>} |
||||||
|
</div> |
||||||
|
<Button onClick={handleLogin} disabled={pending}> |
||||||
|
<Loader className={pending ? 'animate-spin' : 'hidden'} /> |
||||||
|
{t('Login')} |
||||||
|
</Button> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
import { ISigner, TDraftEvent } from '@common/types' |
||||||
|
import { generateSecretKey } from 'nostr-tools' |
||||||
|
import { BunkerSigner as NBunkerSigner, parseBunkerInput } from 'nostr-tools/nip46' |
||||||
|
|
||||||
|
export class BunkerSigner implements ISigner { |
||||||
|
signer: NBunkerSigner | null = null |
||||||
|
clientSecretKey: Uint8Array |
||||||
|
|
||||||
|
constructor(clientSecretKey?: Uint8Array) { |
||||||
|
this.clientSecretKey = clientSecretKey ?? generateSecretKey() |
||||||
|
} |
||||||
|
|
||||||
|
async login(bunker: string): Promise<string> { |
||||||
|
const bunkerPointer = await parseBunkerInput(bunker) |
||||||
|
if (!bunkerPointer) { |
||||||
|
throw new Error('Invalid bunker') |
||||||
|
} |
||||||
|
|
||||||
|
this.signer = new NBunkerSigner(this.clientSecretKey, bunkerPointer, { |
||||||
|
onauth: (url) => { |
||||||
|
window.open(url, '_blank') |
||||||
|
} |
||||||
|
}) |
||||||
|
await this.signer.connect() |
||||||
|
return await this.signer.getPublicKey() |
||||||
|
} |
||||||
|
|
||||||
|
async getPublicKey() { |
||||||
|
if (!this.signer) { |
||||||
|
throw new Error('Not logged in') |
||||||
|
} |
||||||
|
return this.signer.getPublicKey() |
||||||
|
} |
||||||
|
|
||||||
|
async signEvent(draftEvent: TDraftEvent) { |
||||||
|
if (!this.signer) { |
||||||
|
throw new Error('Not logged in') |
||||||
|
} |
||||||
|
return this.signer.signEvent({ |
||||||
|
...draftEvent, |
||||||
|
pubkey: await this.signer.getPublicKey() |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue