16 changed files with 495 additions and 233 deletions
@ -1,4 +1,5 @@ |
|||||||
export const StorageKey = { |
export const StorageKey = { |
||||||
THEME_SETTING: 'themeSetting', |
THEME_SETTING: 'themeSetting', |
||||||
RELAY_GROUPS: 'relayGroups' |
RELAY_GROUPS: 'relayGroups', |
||||||
|
ACCOUNT: 'account' |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,66 @@ |
|||||||
|
import { Button } from '@renderer/components/ui/button' |
||||||
|
import { Input } from '@renderer/components/ui/input' |
||||||
|
import { IS_ELECTRON, isElectron } from '@renderer/lib/env' |
||||||
|
import { useNostr } from '@renderer/providers/NostrProvider' |
||||||
|
import { useEffect, useState } from 'react' |
||||||
|
import { useTranslation } from 'react-i18next' |
||||||
|
|
||||||
|
export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: () => void }) { |
||||||
|
const { t } = useTranslation() |
||||||
|
const { nsecLogin } = useNostr() |
||||||
|
const [nsec, setNsec] = useState('') |
||||||
|
const [errMsg, setErrMsg] = useState<string | null>(null) |
||||||
|
const [storageBackend, setStorageBackend] = useState('unknown') |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const init = async () => { |
||||||
|
if (!isElectron(window)) return |
||||||
|
|
||||||
|
const backend = await window.api.system.getSelectedStorageBackend() |
||||||
|
setStorageBackend(backend) |
||||||
|
} |
||||||
|
init() |
||||||
|
}, []) |
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||||
|
setNsec(e.target.value) |
||||||
|
setErrMsg(null) |
||||||
|
} |
||||||
|
|
||||||
|
const handleLogin = () => { |
||||||
|
if (nsec === '') return |
||||||
|
|
||||||
|
nsecLogin(nsec) |
||||||
|
.then(() => onLoginSuccess()) |
||||||
|
.catch((err) => { |
||||||
|
setErrMsg(err.message) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="text-orange-400"> |
||||||
|
{!IS_ELECTRON |
||||||
|
? t( |
||||||
|
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.' |
||||||
|
) |
||||||
|
: ['unknown', 'basic_text'].includes(storageBackend) |
||||||
|
? t('There are no secret keys stored on this device. Your nsec will be unprotected.') |
||||||
|
: t('Your nsec will be encrypted using the {{backend}}.', { |
||||||
|
backend: storageBackend |
||||||
|
})} |
||||||
|
</div> |
||||||
|
<div className="space-y-1"> |
||||||
|
<Input |
||||||
|
type="password" |
||||||
|
placeholder="nsec1.." |
||||||
|
value={nsec} |
||||||
|
onChange={handleInputChange} |
||||||
|
className={errMsg ? 'border-destructive' : ''} |
||||||
|
/> |
||||||
|
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>} |
||||||
|
</div> |
||||||
|
<Button onClick={handleLogin}>{t('Login')}</Button> |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
@ -1,184 +0,0 @@ |
|||||||
import { TDraftEvent } from '@common/types' |
|
||||||
import LoginDialog from '@renderer/components/LoginDialog' |
|
||||||
import { useToast } from '@renderer/hooks' |
|
||||||
import { useFetchRelayList } from '@renderer/hooks/useFetchRelayList' |
|
||||||
import { IS_ELECTRON, isElectron } from '@renderer/lib/env' |
|
||||||
import client from '@renderer/services/client.service' |
|
||||||
import dayjs from 'dayjs' |
|
||||||
import { Event, kinds } from 'nostr-tools' |
|
||||||
import { createContext, useContext, useEffect, useState } from 'react' |
|
||||||
import { useRelaySettings } from './RelaySettingsProvider' |
|
||||||
|
|
||||||
type TNostrContext = { |
|
||||||
isReady: boolean |
|
||||||
pubkey: string | null |
|
||||||
canLogin: boolean |
|
||||||
login: (nsec: string) => Promise<string> |
|
||||||
logout: () => Promise<void> |
|
||||||
nip07Login: () => Promise<string> |
|
||||||
/** |
|
||||||
* Default publish the event to current relays, user's write relays and additional relays |
|
||||||
*/ |
|
||||||
publish: (draftEvent: TDraftEvent, additionalRelayUrls?: string[]) => Promise<Event> |
|
||||||
signHttpAuth: (url: string, method: string) => Promise<string> |
|
||||||
signEvent: (draftEvent: TDraftEvent) => Promise<Event> |
|
||||||
checkLogin: (cb?: () => void | Promise<void>) => void |
|
||||||
} |
|
||||||
|
|
||||||
const NostrContext = createContext<TNostrContext | undefined>(undefined) |
|
||||||
|
|
||||||
export const useNostr = () => { |
|
||||||
const context = useContext(NostrContext) |
|
||||||
if (!context) { |
|
||||||
throw new Error('useNostr must be used within a NostrProvider') |
|
||||||
} |
|
||||||
return context |
|
||||||
} |
|
||||||
|
|
||||||
export function NostrProvider({ children }: { children: React.ReactNode }) { |
|
||||||
const { toast } = useToast() |
|
||||||
const [isReady, setIsReady] = useState(false) |
|
||||||
const [pubkey, setPubkey] = useState<string | null>(null) |
|
||||||
const [canLogin, setCanLogin] = useState(false) |
|
||||||
const [openLoginDialog, setOpenLoginDialog] = useState(false) |
|
||||||
const { relayUrls: currentRelayUrls } = useRelaySettings() |
|
||||||
const relayList = useFetchRelayList(pubkey) |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
if (window.nostr) { |
|
||||||
window.nostr.getPublicKey().then((pubkey) => { |
|
||||||
if (pubkey) { |
|
||||||
setPubkey(pubkey) |
|
||||||
} |
|
||||||
setIsReady(true) |
|
||||||
}) |
|
||||||
} else { |
|
||||||
setIsReady(true) |
|
||||||
} |
|
||||||
if (isElectron(window)) { |
|
||||||
window.api?.system.isEncryptionAvailable().then((isEncryptionAvailable) => { |
|
||||||
setCanLogin(isEncryptionAvailable) |
|
||||||
}) |
|
||||||
} else { |
|
||||||
setCanLogin(!!window.nostr) |
|
||||||
} |
|
||||||
}, []) |
|
||||||
|
|
||||||
const login = async (nsec: string) => { |
|
||||||
if (!canLogin) { |
|
||||||
throw new Error('encryption is not available') |
|
||||||
} |
|
||||||
if (!isElectron(window)) { |
|
||||||
throw new Error('login is not available') |
|
||||||
} |
|
||||||
const { pubkey, reason } = await window.api.nostr.login(nsec) |
|
||||||
if (!pubkey) { |
|
||||||
throw new Error(reason ?? 'invalid nsec') |
|
||||||
} |
|
||||||
setPubkey(pubkey) |
|
||||||
return pubkey |
|
||||||
} |
|
||||||
|
|
||||||
const nip07Login = async () => { |
|
||||||
if (IS_ELECTRON) { |
|
||||||
throw new Error('electron app should not use nip07 login') |
|
||||||
} |
|
||||||
|
|
||||||
if (!window.nostr) { |
|
||||||
throw new Error( |
|
||||||
'You need to install a nostr signer extension to login. Such as Alby or nos2x' |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
const pubkey = await window.nostr.getPublicKey() |
|
||||||
if (!pubkey) { |
|
||||||
throw new Error('You did not allow to access your pubkey') |
|
||||||
} |
|
||||||
setPubkey(pubkey) |
|
||||||
return pubkey |
|
||||||
} |
|
||||||
|
|
||||||
const logout = async () => { |
|
||||||
if (isElectron(window)) { |
|
||||||
await window.api.nostr.logout() |
|
||||||
} |
|
||||||
setPubkey(null) |
|
||||||
client.clearNotificationsCache() |
|
||||||
} |
|
||||||
|
|
||||||
const publish = async (draftEvent: TDraftEvent, additionalRelayUrls: string[] = []) => { |
|
||||||
const event = await window.nostr?.signEvent(draftEvent) |
|
||||||
if (!event) { |
|
||||||
throw new Error('sign event failed') |
|
||||||
} |
|
||||||
await client.publishEvent( |
|
||||||
relayList.write.concat(additionalRelayUrls).concat(currentRelayUrls), |
|
||||||
event |
|
||||||
) |
|
||||||
return event |
|
||||||
} |
|
||||||
|
|
||||||
const signEvent = async (draftEvent: TDraftEvent) => { |
|
||||||
const event = await window.nostr?.signEvent(draftEvent) |
|
||||||
if (!event) { |
|
||||||
throw new Error('sign event failed') |
|
||||||
} |
|
||||||
return event |
|
||||||
} |
|
||||||
|
|
||||||
const signHttpAuth = async (url: string, method: string) => { |
|
||||||
const event = await window.nostr?.signEvent({ |
|
||||||
content: '', |
|
||||||
kind: kinds.HTTPAuth, |
|
||||||
created_at: dayjs().unix(), |
|
||||||
tags: [ |
|
||||||
['u', url], |
|
||||||
['method', method] |
|
||||||
] |
|
||||||
}) |
|
||||||
if (!event) { |
|
||||||
throw new Error('sign event failed') |
|
||||||
} |
|
||||||
return 'Nostr ' + btoa(JSON.stringify(event)) |
|
||||||
} |
|
||||||
|
|
||||||
const checkLogin = async (cb?: () => void) => { |
|
||||||
if (pubkey) { |
|
||||||
return cb && cb() |
|
||||||
} |
|
||||||
if (IS_ELECTRON) { |
|
||||||
return setOpenLoginDialog(true) |
|
||||||
} |
|
||||||
try { |
|
||||||
await nip07Login() |
|
||||||
} catch (err) { |
|
||||||
toast({ |
|
||||||
title: 'Login failed', |
|
||||||
description: (err as Error).message, |
|
||||||
variant: 'destructive' |
|
||||||
}) |
|
||||||
return |
|
||||||
} |
|
||||||
return cb && cb() |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<NostrContext.Provider |
|
||||||
value={{ |
|
||||||
isReady, |
|
||||||
pubkey, |
|
||||||
canLogin, |
|
||||||
login, |
|
||||||
nip07Login, |
|
||||||
logout, |
|
||||||
publish, |
|
||||||
signHttpAuth, |
|
||||||
checkLogin, |
|
||||||
signEvent |
|
||||||
}} |
|
||||||
> |
|
||||||
{children} |
|
||||||
<LoginDialog open={openLoginDialog} setOpen={setOpenLoginDialog} /> |
|
||||||
</NostrContext.Provider> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -0,0 +1,41 @@ |
|||||||
|
import { ISigner, TDraftEvent } from '@common/types' |
||||||
|
import { bytesToHex } from '@noble/hashes/utils' |
||||||
|
import { finalizeEvent, getPublicKey, nip19 } from 'nostr-tools' |
||||||
|
|
||||||
|
export class BrowserNsecSigner implements ISigner { |
||||||
|
private privkey: Uint8Array | null = null |
||||||
|
private pubkey: string | null = null |
||||||
|
|
||||||
|
login(nsec: string) { |
||||||
|
const { type, data } = nip19.decode(nsec) |
||||||
|
if (type !== 'nsec') { |
||||||
|
throw new Error('invalid nsec') |
||||||
|
} |
||||||
|
|
||||||
|
this.privkey = data |
||||||
|
this.pubkey = getPublicKey(data) |
||||||
|
window.localStorage.setItem('private_key', bytesToHex(data)) |
||||||
|
return this.pubkey |
||||||
|
} |
||||||
|
|
||||||
|
logout() { |
||||||
|
window.localStorage.removeItem('private_key') |
||||||
|
} |
||||||
|
|
||||||
|
async getPublicKey() { |
||||||
|
return this.pubkey |
||||||
|
} |
||||||
|
|
||||||
|
async signEvent(draftEvent: TDraftEvent) { |
||||||
|
if (!this.privkey) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
return finalizeEvent(draftEvent, this.privkey) |
||||||
|
} catch (error) { |
||||||
|
console.error(error) |
||||||
|
return null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,222 @@ |
|||||||
|
import { ISigner, TDraftEvent } from '@common/types' |
||||||
|
import LoginDialog from '@renderer/components/LoginDialog' |
||||||
|
import { useToast } from '@renderer/hooks' |
||||||
|
import { useFetchRelayList } from '@renderer/hooks/useFetchRelayList' |
||||||
|
import { isElectron } from '@renderer/lib/env' |
||||||
|
import client from '@renderer/services/client.service' |
||||||
|
import storage from '@renderer/services/storage.service' |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { Event, kinds } from 'nostr-tools' |
||||||
|
import { createContext, useContext, useEffect, useState } from 'react' |
||||||
|
import { useRelaySettings } from '../RelaySettingsProvider' |
||||||
|
import { BrowserNsecSigner } from './browser-nsec.signer' |
||||||
|
import { Nip07Signer } from './nip-07.signer' |
||||||
|
import { NsecSigner } from './nsec.signer' |
||||||
|
|
||||||
|
type TNostrContext = { |
||||||
|
isReady: boolean |
||||||
|
pubkey: string | null |
||||||
|
setPubkey: (pubkey: string) => void |
||||||
|
nsecLogin: (nsec: string) => Promise<string> |
||||||
|
logout: () => Promise<void> |
||||||
|
nip07Login: () => Promise<void> |
||||||
|
/** |
||||||
|
* Default publish the event to current relays, user's write relays and additional relays |
||||||
|
*/ |
||||||
|
publish: (draftEvent: TDraftEvent, additionalRelayUrls?: string[]) => Promise<Event> |
||||||
|
signHttpAuth: (url: string, method: string) => Promise<string> |
||||||
|
signEvent: (draftEvent: TDraftEvent) => Promise<Event> |
||||||
|
checkLogin: (cb?: () => void | Promise<void>) => void |
||||||
|
} |
||||||
|
|
||||||
|
const NostrContext = createContext<TNostrContext | undefined>(undefined) |
||||||
|
|
||||||
|
export const useNostr = () => { |
||||||
|
const context = useContext(NostrContext) |
||||||
|
if (!context) { |
||||||
|
throw new Error('useNostr must be used within a NostrProvider') |
||||||
|
} |
||||||
|
return context |
||||||
|
} |
||||||
|
|
||||||
|
export function NostrProvider({ children }: { children: React.ReactNode }) { |
||||||
|
const { toast } = useToast() |
||||||
|
const [isReady, setIsReady] = useState(false) |
||||||
|
const [pubkey, setPubkey] = useState<string | null>(null) |
||||||
|
const [signer, setSigner] = useState<ISigner | null>(null) |
||||||
|
const [openLoginDialog, setOpenLoginDialog] = useState(false) |
||||||
|
const { relayUrls: currentRelayUrls } = useRelaySettings() |
||||||
|
const relayList = useFetchRelayList(pubkey) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const init = async () => { |
||||||
|
const account = await storage.getAccountInfo() |
||||||
|
if (!account) { |
||||||
|
if (isElectron(window) || !window.nostr) { |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
|
||||||
|
// For browser env, attempt to login with nip-07
|
||||||
|
const nip07Signer = new Nip07Signer() |
||||||
|
const pubkey = await nip07Signer.getPublicKey() |
||||||
|
if (!pubkey) { |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
setPubkey(pubkey) |
||||||
|
setSigner(nip07Signer) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
|
||||||
|
if (account.signerType === 'nsec') { |
||||||
|
const nsecSigner = new NsecSigner() |
||||||
|
const pubkey = await nsecSigner.getPublicKey() |
||||||
|
if (!pubkey) { |
||||||
|
await storage.setAccountInfo(null) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
setPubkey(pubkey) |
||||||
|
setSigner(nsecSigner) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
|
||||||
|
if (account.signerType === 'browser-nsec') { |
||||||
|
if (!account.nsec) { |
||||||
|
await storage.setAccountInfo(null) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
const browserNsecSigner = new BrowserNsecSigner() |
||||||
|
const pubkey = browserNsecSigner.login(account.nsec) |
||||||
|
setPubkey(pubkey) |
||||||
|
setSigner(browserNsecSigner) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
|
||||||
|
if (account.signerType === 'nip-07') { |
||||||
|
const nip07Signer = new Nip07Signer() |
||||||
|
const pubkey = await nip07Signer.getPublicKey() |
||||||
|
if (!pubkey) { |
||||||
|
await storage.setAccountInfo(null) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
setPubkey(pubkey) |
||||||
|
setSigner(nip07Signer) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
|
||||||
|
await storage.setAccountInfo(null) |
||||||
|
return setIsReady(true) |
||||||
|
} |
||||||
|
init().catch(() => { |
||||||
|
storage.setAccountInfo(null) |
||||||
|
setIsReady(true) |
||||||
|
}) |
||||||
|
}, []) |
||||||
|
|
||||||
|
const nsecLogin = async (nsec: string) => { |
||||||
|
if (isElectron(window)) { |
||||||
|
const nsecSigner = new NsecSigner() |
||||||
|
const { pubkey, reason } = await nsecSigner.login(nsec) |
||||||
|
if (!pubkey) { |
||||||
|
throw new Error(reason ?? 'invalid nsec') |
||||||
|
} |
||||||
|
await storage.setAccountInfo({ signerType: 'nsec' }) |
||||||
|
setPubkey(pubkey) |
||||||
|
setSigner(nsecSigner) |
||||||
|
return pubkey |
||||||
|
} |
||||||
|
const browserNsecSigner = new BrowserNsecSigner() |
||||||
|
const pubkey = browserNsecSigner.login(nsec) |
||||||
|
await storage.setAccountInfo({ signerType: 'browser-nsec', nsec }) |
||||||
|
setPubkey(pubkey) |
||||||
|
setSigner(browserNsecSigner) |
||||||
|
return pubkey |
||||||
|
} |
||||||
|
|
||||||
|
const nip07Login = async () => { |
||||||
|
try { |
||||||
|
const nip07Signer = new Nip07Signer() |
||||||
|
const pubkey = await nip07Signer.getPublicKey() |
||||||
|
if (!pubkey) { |
||||||
|
throw new Error('You did not allow to access your pubkey') |
||||||
|
} |
||||||
|
await storage.setAccountInfo({ signerType: 'nip-07' }) |
||||||
|
setPubkey(pubkey) |
||||||
|
setSigner(nip07Signer) |
||||||
|
} catch (err) { |
||||||
|
toast({ |
||||||
|
title: 'Login failed', |
||||||
|
description: (err as Error).message, |
||||||
|
variant: 'destructive' |
||||||
|
}) |
||||||
|
throw err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const logout = async () => { |
||||||
|
if (signer instanceof NsecSigner) { |
||||||
|
await signer.logout() |
||||||
|
} else if (signer instanceof BrowserNsecSigner) { |
||||||
|
signer.logout() |
||||||
|
} |
||||||
|
setPubkey(null) |
||||||
|
await storage.setAccountInfo(null) |
||||||
|
client.clearNotificationsCache() |
||||||
|
} |
||||||
|
|
||||||
|
const signEvent = async (draftEvent: TDraftEvent) => { |
||||||
|
const event = await signer?.signEvent(draftEvent) |
||||||
|
if (!event) { |
||||||
|
throw new Error('sign event failed') |
||||||
|
} |
||||||
|
return event |
||||||
|
} |
||||||
|
|
||||||
|
const publish = async (draftEvent: TDraftEvent, additionalRelayUrls: string[] = []) => { |
||||||
|
const event = await signEvent(draftEvent) |
||||||
|
await client.publishEvent( |
||||||
|
relayList.write.concat(additionalRelayUrls).concat(currentRelayUrls), |
||||||
|
event |
||||||
|
) |
||||||
|
return event |
||||||
|
} |
||||||
|
|
||||||
|
const signHttpAuth = async (url: string, method: string) => { |
||||||
|
const event = await signEvent({ |
||||||
|
content: '', |
||||||
|
kind: kinds.HTTPAuth, |
||||||
|
created_at: dayjs().unix(), |
||||||
|
tags: [ |
||||||
|
['u', url], |
||||||
|
['method', method] |
||||||
|
] |
||||||
|
}) |
||||||
|
return 'Nostr ' + btoa(JSON.stringify(event)) |
||||||
|
} |
||||||
|
|
||||||
|
const checkLogin = async (cb?: () => void) => { |
||||||
|
if (pubkey) { |
||||||
|
return cb && cb() |
||||||
|
} |
||||||
|
return setOpenLoginDialog(true) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<NostrContext.Provider |
||||||
|
value={{ |
||||||
|
isReady, |
||||||
|
pubkey, |
||||||
|
setPubkey, |
||||||
|
nsecLogin, |
||||||
|
nip07Login, |
||||||
|
logout, |
||||||
|
publish, |
||||||
|
signHttpAuth, |
||||||
|
checkLogin, |
||||||
|
signEvent |
||||||
|
}} |
||||||
|
> |
||||||
|
{children} |
||||||
|
<LoginDialog open={openLoginDialog} setOpen={setOpenLoginDialog} /> |
||||||
|
</NostrContext.Provider> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
import { ISigner, TDraftEvent } from '@common/types' |
||||||
|
import { isElectron } from '@renderer/lib/env' |
||||||
|
|
||||||
|
export class Nip07Signer implements ISigner { |
||||||
|
private signer: ISigner |
||||||
|
|
||||||
|
constructor() { |
||||||
|
if (isElectron(window) || !window.nostr) { |
||||||
|
throw new Error('nip-07 is not available') |
||||||
|
} |
||||||
|
if (!window.nostr) { |
||||||
|
throw new Error( |
||||||
|
'You need to install a nostr signer extension to login. Such as alby, nostr-keyx or nos2x.' |
||||||
|
) |
||||||
|
} |
||||||
|
this.signer = window.nostr |
||||||
|
} |
||||||
|
|
||||||
|
async getPublicKey() { |
||||||
|
return await this.signer.getPublicKey() |
||||||
|
} |
||||||
|
|
||||||
|
async signEvent(draftEvent: TDraftEvent) { |
||||||
|
return await this.signer.signEvent(draftEvent) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
import { ISigner, TDraftEvent, TElectronWindow } from '@common/types' |
||||||
|
import { isElectron } from '@renderer/lib/env' |
||||||
|
|
||||||
|
export class NsecSigner implements ISigner { |
||||||
|
private electronNostrApi: TElectronWindow['api']['nostr'] |
||||||
|
private signer: ISigner |
||||||
|
|
||||||
|
constructor() { |
||||||
|
if (!isElectron(window)) { |
||||||
|
throw new Error('nsec login is not available') |
||||||
|
} |
||||||
|
this.electronNostrApi = window.api.nostr |
||||||
|
this.signer = window.nostr |
||||||
|
} |
||||||
|
|
||||||
|
async login(nsec: string) { |
||||||
|
return await this.electronNostrApi.login(nsec) |
||||||
|
} |
||||||
|
|
||||||
|
async logout() { |
||||||
|
return await this.electronNostrApi.logout() |
||||||
|
} |
||||||
|
|
||||||
|
async getPublicKey() { |
||||||
|
return await this.signer.getPublicKey() |
||||||
|
} |
||||||
|
|
||||||
|
async signEvent(draftEvent: TDraftEvent) { |
||||||
|
return await this.signer.signEvent(draftEvent) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue