From 33ac5e60b676846976202da2e345f6b4c18b21e7 Mon Sep 17 00:00:00 2001 From: codytseng Date: Mon, 23 Dec 2024 23:22:49 +0800 Subject: [PATCH] feat: multi accounts --- .../AccountButton/ProfileButton.tsx | 18 +- src/components/AccountButton/index.tsx | 2 +- src/components/AccountList/index.tsx | 76 ++++++++ .../BunkerLogin.tsx | 11 +- .../NsecLogin.tsx | 11 +- src/components/AccountManager/index.tsx | 64 +++++++ src/components/LoginDialog/index.tsx | 49 +---- src/components/UserAvatar/index.tsx | 32 ++++ src/components/Username/index.tsx | 24 +++ src/components/ui/badge.tsx | 33 ++++ src/constants.ts | 3 +- src/i18n/en.ts | 5 +- src/i18n/zh.ts | 5 +- src/lib/account.ts | 5 + src/providers/NostrProvider/index.tsx | 176 ++++++++++-------- src/services/storage.service.ts | 43 ++++- src/types.ts | 6 +- 17 files changed, 426 insertions(+), 137 deletions(-) create mode 100644 src/components/AccountList/index.tsx rename src/components/{LoginDialog => AccountManager}/BunkerLogin.tsx (86%) rename src/components/{LoginDialog => AccountManager}/NsecLogin.tsx (86%) create mode 100644 src/components/AccountManager/index.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/lib/account.ts diff --git a/src/components/AccountButton/ProfileButton.tsx b/src/components/AccountButton/ProfileButton.tsx index 51f848c5..c72f5edf 100644 --- a/src/components/AccountButton/ProfileButton.tsx +++ b/src/components/AccountButton/ProfileButton.tsx @@ -11,19 +11,22 @@ import { toProfile } from '@/lib/link' import { formatPubkey, generateImageByPubkey } from '@/lib/pubkey' import { useSecondaryPage } from '@/PageManager' import { useNostr } from '@/providers/NostrProvider' +import { useState } from 'react' import { useTranslation } from 'react-i18next' +import LoginDialog from '../LoginDialog' export default function ProfileButton({ - pubkey, variant = 'titlebar' }: { - pubkey: string variant?: 'titlebar' | 'sidebar' | 'small-screen-titlebar' }) { const { t } = useTranslation() - const { logout } = useNostr() + const { removeAccount, account } = useNostr() + const pubkey = account?.pubkey const { profile } = useFetchProfile(pubkey) const { push } = useSecondaryPage() + const [loginDialogOpen, setLoginDialogOpen] = useState(false) + if (!pubkey) return null const defaultAvatar = generateImageByPubkey(pubkey) const { username, avatar } = profile || { username: formatPubkey(pubkey), avatar: defaultAvatar } @@ -72,10 +75,17 @@ export default function ProfileButton({ {triggerComponent} push(toProfile(pubkey))}>{t('Profile')} - + setLoginDialogOpen(true)}> + {t('Manage accounts')} + + removeAccount(account)} + > {t('Logout')} + ) } diff --git a/src/components/AccountButton/index.tsx b/src/components/AccountButton/index.tsx index 531e303b..bfc5674f 100644 --- a/src/components/AccountButton/index.tsx +++ b/src/components/AccountButton/index.tsx @@ -10,7 +10,7 @@ export default function AccountButton({ const { pubkey } = useNostr() if (pubkey) { - return + return } else { return } diff --git a/src/components/AccountList/index.tsx b/src/components/AccountList/index.tsx new file mode 100644 index 00000000..537d884a --- /dev/null +++ b/src/components/AccountList/index.tsx @@ -0,0 +1,76 @@ +import { Badge } from '@/components/ui/badge' +import { isSameAccount } from '@/lib/account' +import { formatPubkey } from '@/lib/pubkey' +import { cn } from '@/lib/utils' +import { useNostr } from '@/providers/NostrProvider' +import { TAccountPointer, TSignerType } from '@/types' +import { Loader, Trash2 } from 'lucide-react' +import { useState } from 'react' +import { SimpleUserAvatar } from '../UserAvatar' +import { SimpleUsername } from '../Username' + +export default function AccountList({ afterSwitch }: { afterSwitch: () => void }) { + const { accounts, account, switchAccount, removeAccount } = useNostr() + const [switchingAccount, setSwitchingAccount] = useState(null) + + return ( +
+ {accounts.map((act) => ( +
{ + if (isSameAccount(act, account)) return + setSwitchingAccount(act) + switchAccount(act) + .then(() => afterSwitch()) + .finally(() => setSwitchingAccount(null)) + }} + > +
+
+ +
+ +
+ {formatPubkey(act.pubkey)} +
+
+
+
+ + { + e.stopPropagation() + removeAccount(act) + }} + /> +
+
+ {switchingAccount && isSameAccount(act, switchingAccount) && ( +
+ +
+ )} +
+ ))} +
+ ) +} + +function SignerTypeBadge({ signerType }: { signerType: TSignerType }) { + if (signerType === 'nip-07') { + return NIP-07 + } else if (signerType === 'bunker') { + return Bunker + } else { + return NSEC + } +} diff --git a/src/components/LoginDialog/BunkerLogin.tsx b/src/components/AccountManager/BunkerLogin.tsx similarity index 86% rename from src/components/LoginDialog/BunkerLogin.tsx rename to src/components/AccountManager/BunkerLogin.tsx index bfd05d7e..0cb3c924 100644 --- a/src/components/LoginDialog/BunkerLogin.tsx +++ b/src/components/AccountManager/BunkerLogin.tsx @@ -5,7 +5,13 @@ import { Loader } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -export default function BunkerLogin({ onLoginSuccess }: { onLoginSuccess: () => void }) { +export default function BunkerLogin({ + back, + onLoginSuccess +}: { + back: () => void + onLoginSuccess: () => void +}) { const { t } = useTranslation() const { bunkerLogin } = useNostr() const [pending, setPending] = useState(false) @@ -42,6 +48,9 @@ export default function BunkerLogin({ onLoginSuccess }: { onLoginSuccess: () => {t('Login')} + ) } diff --git a/src/components/LoginDialog/NsecLogin.tsx b/src/components/AccountManager/NsecLogin.tsx similarity index 86% rename from src/components/LoginDialog/NsecLogin.tsx rename to src/components/AccountManager/NsecLogin.tsx index f0698c8f..286a14db 100644 --- a/src/components/LoginDialog/NsecLogin.tsx +++ b/src/components/AccountManager/NsecLogin.tsx @@ -4,7 +4,13 @@ import { useNostr } from '@/providers/NostrProvider' import { useState } from 'react' import { useTranslation } from 'react-i18next' -export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: () => void }) { +export default function PrivateKeyLogin({ + back, + onLoginSuccess +}: { + back: () => void + onLoginSuccess: () => void +}) { const { t } = useTranslation() const { nsecLogin } = useNostr() const [nsec, setNsec] = useState('') @@ -43,6 +49,9 @@ export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: () {errMsg &&
{errMsg}
} + ) } diff --git a/src/components/AccountManager/index.tsx b/src/components/AccountManager/index.tsx new file mode 100644 index 00000000..b0089a03 --- /dev/null +++ b/src/components/AccountManager/index.tsx @@ -0,0 +1,64 @@ +import { Button } from '@/components/ui/button' +import { Separator } from '@/components/ui/separator' +import { useNostr } from '@/providers/NostrProvider' +import { TSignerType } from '@/types' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import AccountList from '../AccountList' +import BunkerLogin from './BunkerLogin' +import PrivateKeyLogin from './NsecLogin' + +export default function AccountManager({ close }: { close: () => void }) { + const [loginMethod, setLoginMethod] = useState(null) + + return ( + <> + {loginMethod === 'nsec' ? ( + setLoginMethod(null)} onLoginSuccess={() => close()} /> + ) : loginMethod === 'bunker' ? ( + setLoginMethod(null)} onLoginSuccess={() => close()} /> + ) : ( + + )} + + ) +} + +function AccountManagerNav({ + setLoginMethod, + close +}: { + setLoginMethod: (method: TSignerType) => void + close: () => void +}) { + const { t } = useTranslation() + const { nip07Login, accounts } = useNostr() + + return ( + <> +
+ {t('Add an Account')} +
+ {!!window.nostr && ( + + )} + + + {accounts.length > 0 && ( + <> + +
+ {t('Logged in Accounts')} +
+ close()} /> + + )} + + ) +} diff --git a/src/components/LoginDialog/index.tsx b/src/components/LoginDialog/index.tsx index 899ff570..8b6ee78e 100644 --- a/src/components/LoginDialog/index.tsx +++ b/src/components/LoginDialog/index.tsx @@ -1,4 +1,3 @@ -import { Button } from '@/components/ui/button' import { Dialog, DialogContent, @@ -6,12 +5,8 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { useNostr } from '@/providers/NostrProvider' -import { ArrowLeft } from 'lucide-react' -import { Dispatch, useState } from 'react' -import { useTranslation } from 'react-i18next' -import BunkerLogin from './BunkerLogin' -import PrivateKeyLogin from './NsecLogin' +import { Dispatch } from 'react' +import AccountManager from '../AccountManager' export default function LoginDialog({ open, @@ -20,10 +15,6 @@ export default function LoginDialog({ open: boolean setOpen: Dispatch }) { - const { t } = useTranslation() - const [loginMethod, setLoginMethod] = useState<'nsec' | 'nip07' | 'bunker' | null>(null) - const { nip07Login } = useNostr() - return ( @@ -31,41 +22,7 @@ export default function LoginDialog({ - {loginMethod === 'nsec' ? ( - <> -
setLoginMethod(null)} - > - -
- setOpen(false)} /> - - ) : loginMethod === 'bunker' ? ( - <> -
setLoginMethod(null)} - > - -
- setOpen(false)} /> - - ) : ( - <> - {!!window.nostr && ( - - )} - - - - )} + setOpen(false)} />
) diff --git a/src/components/UserAvatar/index.tsx b/src/components/UserAvatar/index.tsx index afb55a1a..9c883e59 100644 --- a/src/components/UserAvatar/index.tsx +++ b/src/components/UserAvatar/index.tsx @@ -54,3 +54,35 @@ export default function UserAvatar({ ) } + +export function SimpleUserAvatar({ + userId, + size = 'normal', + className, + onClick +}: { + userId: string + size?: 'large' | 'normal' | 'small' | 'tiny' + className?: string + onClick?: (e: React.MouseEvent) => void +}) { + const { profile } = useFetchProfile(userId) + const defaultAvatar = useMemo( + () => (profile?.pubkey ? generateImageByPubkey(profile.pubkey) : ''), + [profile] + ) + + if (!profile) { + return + } + const { avatar, pubkey } = profile + + return ( + + + + {pubkey} + + + ) +} diff --git a/src/components/Username/index.tsx b/src/components/Username/index.tsx index 44a99987..9b96a7aa 100644 --- a/src/components/Username/index.tsx +++ b/src/components/Username/index.tsx @@ -42,3 +42,27 @@ export default function Username({ ) } + +export function SimpleUsername({ + userId, + showAt = false, + className, + skeletonClassName +}: { + userId: string + showAt?: boolean + className?: string + skeletonClassName?: string +}) { + const { profile } = useFetchProfile(userId) + if (!profile) return + + const { username } = profile + + return ( +
+ {showAt && '@'} + {username} +
+ ) +} diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 00000000..48b518a4 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center rounded-md border px-2.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + { + variants: { + variant: { + default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', + secondary: + 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', + destructive: + 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + outline: 'text-foreground' + } + }, + defaultVariants: { + variant: 'default' + } + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return
+} + +export { Badge, badgeVariants } diff --git a/src/constants.ts b/src/constants.ts index c79fb9d5..058581fb 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,8 @@ export const StorageKey = { THEME_SETTING: 'themeSetting', RELAY_GROUPS: 'relayGroups', - ACCOUNTS: 'accounts' + ACCOUNTS: 'accounts', + CURRENT_ACCOUNT: 'currentAccount' } export const BIG_RELAY_URLS = [ diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 04868271..70b8ed71 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -84,6 +84,9 @@ export default { 'Login with Browser Extension': 'Login with Browser Extension', 'Login with Bunker': 'Login with Bunker', 'Login with Private Key': 'Login with Private Key', - 'reload notes': 'reload notes' + 'reload notes': 'reload notes', + 'Logged in Accounts': 'Logged in Accounts', + 'Add an Account': 'Add an Account', + 'Manage accounts': 'Manage accounts' } } diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index fdd4661e..05ff7c61 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -83,6 +83,9 @@ export default { 'Login with Browser Extension': '浏览器插件登录', 'Login with Bunker': 'Bunker 登录', 'Login with Private Key': '私钥登录', - 'reload notes': '重新加载笔记' + 'reload notes': '重新加载笔记', + 'Logged in Accounts': '已登录账户', + 'Add an Account': '添加账户', + 'Manage accounts': '多帐户管理' } } diff --git a/src/lib/account.ts b/src/lib/account.ts new file mode 100644 index 00000000..be9f88d0 --- /dev/null +++ b/src/lib/account.ts @@ -0,0 +1,5 @@ +import { TAccountPointer } from '@/types' + +export function isSameAccount(a: TAccountPointer | null, b: TAccountPointer | null) { + return a?.pubkey === b?.pubkey && a?.signerType === b?.signerType +} diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index a0785b23..e723437f 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -3,22 +3,24 @@ import { useToast } from '@/hooks' import { useFetchRelayList } from '@/hooks/useFetchRelayList' import client from '@/services/client.service' import storage from '@/services/storage.service' -import { ISigner, TDraftEvent } from '@/types' +import { ISigner, TAccount, TAccountPointer, TDraftEvent } from '@/types' import dayjs from 'dayjs' import { Event, kinds } from 'nostr-tools' import { createContext, useContext, useEffect, useState } from 'react' import { useRelaySettings } from '../RelaySettingsProvider' -import { NsecSigner } from './nsec.signer' import { BunkerSigner } from './bunker.signer' import { Nip07Signer } from './nip-07.signer' +import { NsecSigner } from './nsec.signer' type TNostrContext = { pubkey: string | null - setPubkey: (pubkey: string) => void + account: TAccountPointer | null + accounts: TAccountPointer[] + switchAccount: (account: TAccountPointer | null) => Promise nsecLogin: (nsec: string) => Promise nip07Login: () => Promise bunkerLogin: (bunker: string) => Promise - logout: () => void + removeAccount: (account: TAccountPointer) => void /** * Default publish the event to current relays, user's write relays and additional relays */ @@ -40,83 +42,61 @@ export const useNostr = () => { export function NostrProvider({ children }: { children: React.ReactNode }) { const { toast } = useToast() - const [pubkey, setPubkey] = useState(null) + const [account, setAccount] = useState(null) const [signer, setSigner] = useState(null) const [openLoginDialog, setOpenLoginDialog] = useState(false) const { relayUrls: currentRelayUrls } = useRelaySettings() - const relayList = useFetchRelayList(pubkey) + const relayList = useFetchRelayList(account?.pubkey) useEffect(() => { const init = async () => { - const [account] = storage.getAccounts() - if (!account) { - if (!window.nostr) { - return - } + const accounts = storage.getAccounts() + const act = storage.getCurrentAccount() ?? accounts[0] // auto login the first account + if (!act) return - // For browser env, attempt to login with nip-07 - const nip07Signer = new Nip07Signer() - const pubkey = await nip07Signer.getPublicKey() - if (!pubkey) { - return - } - storage.setAccounts([{ pubkey, signerType: 'nip-07' }]) - return login(nip07Signer, pubkey) + setAccount({ pubkey: act.pubkey, signerType: act.signerType }) + + const pubkey = await loginWithAccountPointer(act) + // login failed, set account to null + if (!pubkey) { + setAccount(null) + return } - - if (account.pubkey) { - setPubkey(account.pubkey) - } - - // browser-nsec is deprecated - if (account.signerType === 'browser-nsec') { - if (account.nsec) { - const browserNsecSigner = new NsecSigner() - const pubkey = browserNsecSigner.login(account.nsec) - storage.setAccounts([{ pubkey, signerType: 'nsec', nsec: account.nsec }]) - return login(browserNsecSigner, pubkey) - } - } else if (account.signerType === 'nsec') { - if (account.nsec) { - const browserNsecSigner = new NsecSigner() - const pubkey = browserNsecSigner.login(account.nsec) - return login(browserNsecSigner, pubkey) - } - } else if (account.signerType === 'nip-07') { - const nip07Signer = new Nip07Signer() - return login(nip07Signer, account.pubkey) - } else if (account.signerType === 'bunker') { - if (account.bunker && account.bunkerClientSecretKey) { - const bunkerSigner = new BunkerSigner(account.bunkerClientSecretKey) - const pubkey = await bunkerSigner.login(account.bunker) - return login(bunkerSigner, pubkey) - } - } - - return logout() } init().catch(() => { - logout() + setAccount(null) }) }, []) - const login = (signer: ISigner, pubkey: string) => { - setPubkey(pubkey) + const login = (signer: ISigner, act: TAccount) => { + storage.addAccount(act) + storage.switchAccount(act) + setAccount({ pubkey: act.pubkey, signerType: act.signerType }) setSigner(signer) - return pubkey + return act.pubkey } - const logout = () => { - setPubkey(null) - setSigner(null) - storage.setAccounts([]) + const removeAccount = (act: TAccountPointer) => { + storage.removeAccount(act) + if (account?.pubkey === act.pubkey) { + setAccount(null) + setSigner(null) + } + } + + const switchAccount = async (act: TAccountPointer | null) => { + if (!act) { + storage.switchAccount(null) + setAccount(null) + return + } + await loginWithAccountPointer(act) } const nsecLogin = async (nsec: string) => { const browserNsecSigner = new NsecSigner() const pubkey = browserNsecSigner.login(nsec) - storage.setAccounts([{ pubkey, signerType: 'nsec', nsec }]) - return login(browserNsecSigner, pubkey) + return login(browserNsecSigner, { pubkey, signerType: 'nsec', nsec }) } const nip07Login = async () => { @@ -126,8 +106,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { if (!pubkey) { throw new Error('You did not allow to access your pubkey') } - storage.setAccounts([{ pubkey, signerType: 'nip-07' }]) - return login(nip07Signer, pubkey) + return login(nip07Signer, { pubkey, signerType: 'nip-07' }) } catch (err) { toast({ title: 'Login failed', @@ -146,15 +125,62 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { } const bunkerUrl = new URL(bunker) bunkerUrl.searchParams.delete('secret') - storage.setAccounts([ - { - pubkey, - signerType: 'bunker', - bunker: bunkerUrl.toString(), - bunkerClientSecretKey: bunkerSigner.getClientSecretKey() + return login(bunkerSigner, { + pubkey, + signerType: 'bunker', + bunker: bunkerUrl.toString(), + bunkerClientSecretKey: bunkerSigner.getClientSecretKey() + }) + } + + const loginWithAccountPointer = async (act: TAccountPointer): Promise => { + let account = storage.findAccount(act) + if (!account) { + return null + } + if (account.signerType === 'nsec' || account.signerType === 'browser-nsec') { + if (account.nsec) { + const browserNsecSigner = new NsecSigner() + browserNsecSigner.login(account.nsec) + // Migrate to nsec + if (account.signerType === 'browser-nsec') { + storage.removeAccount(account) + account = { ...account, signerType: 'nsec' } + storage.addAccount(account) + } + return login(browserNsecSigner, account) } - ]) - return login(bunkerSigner, pubkey) + } else if (account.signerType === 'nip-07') { + const nip07Signer = new Nip07Signer() + const pubkey = await nip07Signer.getPublicKey() + if (!pubkey) { + storage.removeAccount(account) + return null + } + if (pubkey !== account.pubkey) { + storage.removeAccount(account) + account = { ...account, pubkey } + storage.addAccount(account) + } + return login(nip07Signer, account) + } else if (account.signerType === 'bunker') { + if (account.bunker && account.bunkerClientSecretKey) { + const bunkerSigner = new BunkerSigner(account.bunkerClientSecretKey) + const pubkey = await bunkerSigner.login(account.bunker) + if (!pubkey) { + storage.removeAccount(account) + return null + } + if (pubkey !== account.pubkey) { + storage.removeAccount(account) + account = { ...account, pubkey } + storage.addAccount(account) + } + return login(bunkerSigner, account) + } + } + storage.removeAccount(account) + return null } const signEvent = async (draftEvent: TDraftEvent) => { @@ -197,12 +223,16 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { return ( ({ pubkey: act.pubkey, signerType: act.signerType })), + switchAccount, nsecLogin, nip07Login, bunkerLogin, - logout, + removeAccount, publish, signHttpAuth, checkLogin, diff --git a/src/services/storage.service.ts b/src/services/storage.service.ts index 5becbe1c..a1ddfc34 100644 --- a/src/services/storage.service.ts +++ b/src/services/storage.service.ts @@ -1,5 +1,6 @@ import { StorageKey } from '@/constants' -import { TAccount, TRelayGroup, TThemeSetting } from '@/types' +import { isSameAccount } from '@/lib/account' +import { TAccount, TRelayGroup, TAccountPointer, TThemeSetting } from '@/types' const DEFAULT_RELAY_GROUPS: TRelayGroup[] = [ { @@ -15,6 +16,7 @@ class StorageService { private relayGroups: TRelayGroup[] = [] private themeSetting: TThemeSetting = 'system' private accounts: TAccount[] = [] + private currentAccount: TAccount | null = null constructor() { if (!StorageService.instance) { @@ -31,6 +33,8 @@ class StorageService { (window.localStorage.getItem(StorageKey.THEME_SETTING) as TThemeSetting) ?? 'system' const accountsStr = window.localStorage.getItem(StorageKey.ACCOUNTS) this.accounts = accountsStr ? JSON.parse(accountsStr) : [] + const currentAccountStr = window.localStorage.getItem(StorageKey.CURRENT_ACCOUNT) + this.currentAccount = currentAccountStr ? JSON.parse(currentAccountStr) : null } getRelayGroups() { @@ -55,13 +59,38 @@ class StorageService { return this.accounts } - setAccounts(accounts: TAccount[]) { - if (accounts === null) { - window.localStorage.removeItem(StorageKey.ACCOUNTS) - } else { - window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(accounts)) + findAccount(account: TAccountPointer) { + return this.accounts.find((act) => isSameAccount(act, account)) + } + + getCurrentAccount() { + return this.currentAccount + } + + addAccount(account: TAccount) { + if (this.accounts.find((act) => isSameAccount(act, account))) { + return } - this.accounts = accounts + this.accounts.push(account) + window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) + return account + } + + removeAccount(account: TAccount) { + this.accounts = this.accounts.filter((act) => !isSameAccount(act, account)) + window.localStorage.setItem(StorageKey.ACCOUNTS, JSON.stringify(this.accounts)) + } + + switchAccount(account: TAccount | null) { + if (isSameAccount(this.currentAccount, account)) { + return + } + const act = this.accounts.find((act) => isSameAccount(act, account)) + if (!act) { + return + } + this.currentAccount = act + window.localStorage.setItem(StorageKey.CURRENT_ACCOUNT, JSON.stringify(act)) } } diff --git a/src/types.ts b/src/types.ts index 012acb06..1c39ac13 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,10 +52,14 @@ export interface ISigner { signEvent: (draftEvent: TDraftEvent) => Promise } +export type TSignerType = 'nsec' | 'nip-07' | 'bunker' | 'browser-nsec' + export type TAccount = { pubkey: string - signerType: 'nsec' | 'browser-nsec' | 'nip-07' | 'bunker' + signerType: TSignerType nsec?: string bunker?: string bunkerClientSecretKey?: string } + +export type TAccountPointer = Pick