11 changed files with 196 additions and 30 deletions
@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
|
||||
export const StorageKey = { |
||||
THEME_SETTING: 'themeSetting', |
||||
RELAY_GROUPS: 'relayGroups', |
||||
ACCOUNT: 'account' |
||||
ACCOUNTS: 'accounts' |
||||
} |
||||
|
||||
@ -0,0 +1,47 @@
@@ -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 @@
@@ -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