diff --git a/src/providers/FollowListProvider.tsx b/src/providers/FollowListProvider.tsx
index 12351d6..c9ab0bb 100644
--- a/src/providers/FollowListProvider.tsx
+++ b/src/providers/FollowListProvider.tsx
@@ -1,8 +1,7 @@
+import { createFollowListDraftEvent } from '@/lib/draft-event'
import { tagNameEquals } from '@/lib/tag'
import client from '@/services/client.service'
-import { TDraftEvent } from '@/types'
-import dayjs from 'dayjs'
-import { Event, kinds } from 'nostr-tools'
+import { Event } from 'nostr-tools'
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
import { useNostr } from './NostrProvider'
@@ -57,12 +56,10 @@ export function FollowListProvider({ children }: { children: React.ReactNode })
const follow = async (pubkey: string) => {
if (isFetching || !accountPubkey) return
- const newFollowListDraftEvent: TDraftEvent = {
- kind: kinds.Contacts,
- content: followListEvent?.content ?? '',
- created_at: dayjs().unix(),
- tags: (followListEvent?.tags ?? []).concat([['p', pubkey]])
- }
+ const newFollowListDraftEvent = createFollowListDraftEvent(
+ (followListEvent?.tags ?? []).concat([['p', pubkey]]),
+ followListEvent?.content
+ )
const newFollowListEvent = await publish(newFollowListDraftEvent)
client.updateFollowListCache(accountPubkey, newFollowListEvent)
updateFollowListEvent(newFollowListEvent)
@@ -72,14 +69,10 @@ export function FollowListProvider({ children }: { children: React.ReactNode })
const unfollow = async (pubkey: string) => {
if (isFetching || !accountPubkey || !followListEvent) return
- const newFollowListDraftEvent: TDraftEvent = {
- kind: kinds.Contacts,
- content: followListEvent.content ?? '',
- created_at: dayjs().unix(),
- tags: followListEvent.tags.filter(
- ([tagName, tagValue]) => tagName !== 'p' || tagValue !== pubkey
- )
- }
+ const newFollowListDraftEvent = createFollowListDraftEvent(
+ followListEvent.tags.filter(([tagName, tagValue]) => tagName !== 'p' || tagValue !== pubkey),
+ followListEvent.content
+ )
const newFollowListEvent = await publish(newFollowListDraftEvent)
client.updateFollowListCache(accountPubkey, newFollowListEvent)
updateFollowListEvent(newFollowListEvent)
diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx
index ecef760..d5ab46a 100644
--- a/src/providers/NostrProvider/index.tsx
+++ b/src/providers/NostrProvider/index.tsx
@@ -1,10 +1,12 @@
import LoginDialog from '@/components/LoginDialog'
+import { BIG_RELAY_URLS } from '@/constants'
import { useToast } from '@/hooks'
import {
getFollowingsFromFollowListEvent,
getProfileFromProfileEvent,
getRelayListFromRelayListEvent
} from '@/lib/event'
+import { formatPubkey } from '@/lib/pubkey'
import client from '@/services/client.service'
import storage from '@/services/storage.service'
import { ISigner, TAccount, TAccountPointer, TDraftEvent, TProfile, TRelayList } from '@/types'
@@ -18,10 +20,12 @@ import { NsecSigner } from './nsec.signer'
type TNostrContext = {
pubkey: string | null
profile: TProfile | null
+ profileEvent: Event | null
relayList: TRelayList | null
followings: string[] | null
account: TAccountPointer | null
accounts: TAccountPointer[]
+ nsec: string | null
switchAccount: (account: TAccountPointer | null) => Promise
nsecLogin: (nsec: string) => Promise
nip07Login: () => Promise
@@ -38,6 +42,7 @@ type TNostrContext = {
updateRelayListEvent: (relayListEvent: Event) => void
getFollowings: (pubkey: string) => Promise
updateFollowListEvent: (followListEvent: Event) => void
+ updateProfileEvent: (profileEvent: Event) => void
}
const NostrContext = createContext(undefined)
@@ -53,9 +58,11 @@ export const useNostr = () => {
export function NostrProvider({ children }: { children: React.ReactNode }) {
const { toast } = useToast()
const [account, setAccount] = useState(null)
+ const [nsec, setNsec] = useState(null)
const [signer, setSigner] = useState(null)
const [openLoginDialog, setOpenLoginDialog] = useState(false)
const [profile, setProfile] = useState(null)
+ const [profileEvent, setProfileEvent] = useState(null)
const [relayList, setRelayList] = useState(null)
const [followings, setFollowings] = useState(null)
@@ -71,43 +78,69 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
}, [])
useEffect(() => {
+ setRelayList(null)
+ setFollowings(null)
+ setProfile(null)
+ setProfileEvent(null)
+ setNsec(null)
if (!account) {
- setRelayList(null)
- setFollowings(null)
- setProfile(null)
return
}
+ const storedNsec = storage.getAccountNsec(account.pubkey)
+ if (storedNsec) {
+ setNsec(storedNsec)
+ }
const storedRelayListEvent = storage.getAccountRelayListEvent(account.pubkey)
if (storedRelayListEvent) {
setRelayList(
storedRelayListEvent ? getRelayListFromRelayListEvent(storedRelayListEvent) : null
)
}
- const followListEvent = storage.getAccountFollowListEvent(account.pubkey)
- if (followListEvent) {
- setFollowings(getFollowingsFromFollowListEvent(followListEvent))
+ const storedFollowListEvent = storage.getAccountFollowListEvent(account.pubkey)
+ if (storedFollowListEvent) {
+ setFollowings(getFollowingsFromFollowListEvent(storedFollowListEvent))
}
- const profileEvent = storage.getAccountProfileEvent(account.pubkey)
- if (profileEvent) {
- setProfile(getProfileFromProfileEvent(profileEvent))
+ const storedProfileEvent = storage.getAccountProfileEvent(account.pubkey)
+ if (storedProfileEvent) {
+ setProfileEvent(storedProfileEvent)
+ setProfile(getProfileFromProfileEvent(storedProfileEvent))
}
- client.fetchRelayListEvent(account.pubkey).then((relayListEvent) => {
- if (!relayListEvent) return
+ client.fetchRelayListEvent(account.pubkey).then(async (relayListEvent) => {
+ if (!relayListEvent) {
+ if (storedRelayListEvent) return
+
+ setRelayList({ write: BIG_RELAY_URLS, read: BIG_RELAY_URLS })
+ return
+ }
const isNew = storage.setAccountRelayListEvent(relayListEvent)
if (!isNew) return
setRelayList(getRelayListFromRelayListEvent(relayListEvent))
})
- client.fetchFollowListEvent(account.pubkey).then((followListEvent) => {
- if (!followListEvent) return
+ client.fetchFollowListEvent(account.pubkey).then(async (followListEvent) => {
+ if (!followListEvent) {
+ if (storedFollowListEvent) return
+
+ setFollowings([])
+ return
+ }
const isNew = storage.setAccountFollowListEvent(followListEvent)
if (!isNew) return
setFollowings(getFollowingsFromFollowListEvent(followListEvent))
})
- client.fetchProfileEvent(account.pubkey).then((profileEvent) => {
- if (!profileEvent) return
+ client.fetchProfileEvent(account.pubkey).then(async (profileEvent) => {
+ if (!profileEvent) {
+ if (storedProfileEvent) return
+
+ setProfile({
+ pubkey: account.pubkey,
+ username: formatPubkey(account.pubkey)
+ })
+ return
+ }
const isNew = storage.setAccountProfileEvent(profileEvent)
if (!isNew) return
+ setProfileEvent(profileEvent)
setProfile(getProfileFromProfileEvent(profileEvent))
})
}, [account])
@@ -280,17 +313,27 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
setFollowings(getFollowingsFromFollowListEvent(followListEvent))
}
+ const updateProfileEvent = (profileEvent: Event) => {
+ const isNew = storage.setAccountProfileEvent(profileEvent)
+ if (!isNew) return
+ setProfileEvent(profileEvent)
+ setProfile(getProfileFromProfileEvent(profileEvent))
+ client.updateProfileCache(profileEvent)
+ }
+
return (
({ pubkey: act.pubkey, signerType: act.signerType })),
+ nsec,
switchAccount,
nsecLogin,
nip07Login,
@@ -303,7 +346,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
getRelayList,
updateRelayListEvent,
getFollowings,
- updateFollowListEvent
+ updateFollowListEvent,
+ updateProfileEvent
}}
>
{children}
diff --git a/src/routes.tsx b/src/routes.tsx
index 019a6d1..9bfcb35 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -4,6 +4,7 @@ import FollowingListPage from './pages/secondary/FollowingListPage'
import HomePage from './pages/secondary/HomePage'
import NoteListPage from './pages/secondary/NoteListPage'
import NotePage from './pages/secondary/NotePage'
+import ProfileEditorPage from './pages/secondary/ProfileEditorPage'
import ProfileListPage from './pages/secondary/ProfileListPage'
import ProfilePage from './pages/secondary/ProfilePage'
import RelaySettingsPage from './pages/secondary/RelaySettingsPage'
@@ -17,7 +18,8 @@ const ROUTES = [
{ path: '/users/:id', element: },
{ path: '/users/:id/following', element: },
{ path: '/relay-settings', element: },
- { path: '/settings', element: }
+ { path: '/settings', element: },
+ { path: '/profile-editor', element: }
]
export const routes = ROUTES.map(({ path, element }) => ({
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index 85f020f..d941b7a 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -399,6 +399,11 @@ class ClientService extends EventTarget {
}
}
+ updateProfileCache(event: NEvent) {
+ this.profileEventDataloader.clear(event.pubkey)
+ this.profileEventDataloader.prime(event.pubkey, Promise.resolve(event))
+ }
+
async fetchProfiles(relayUrls: string[], filter: Filter): Promise {
const events = await this.pool.querySync(relayUrls, {
...filter,
diff --git a/src/services/storage.service.ts b/src/services/storage.service.ts
index 0702a1c..637d01b 100644
--- a/src/services/storage.service.ts
+++ b/src/services/storage.service.ts
@@ -151,6 +151,11 @@ class StorageService {
return this.currentAccount
}
+ getAccountNsec(pubkey: string) {
+ const account = this.accounts.find((act) => act.pubkey === pubkey && act.signerType === 'nsec')
+ return account?.nsec
+ }
+
addAccount(account: TAccount) {
if (this.accounts.find((act) => isSameAccount(act, account))) {
return
diff --git a/src/types.ts b/src/types.ts
index a861df1..c0dc785 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,6 +3,7 @@ import { Event } from 'nostr-tools'
export type TProfile = {
username: string
pubkey: string
+ original_username?: string
banner?: string
avatar?: string
nip05?: string
@@ -69,3 +70,9 @@ export type TFeedType = 'following' | 'relays' | 'temporary'
export type TLanguage = 'en' | 'zh'
export type TImageInfo = { url: string; blurHash?: string; dim?: { width: number; height: number } }
+
+export type TMailboxRelayScope = 'read' | 'write' | 'both'
+export type TMailboxRelay = {
+ url: string
+ scope: TMailboxRelayScope
+}