void }>(null)
- const { feedType } = useFeed()
- const { relayUrls, temporaryRelayUrls } = useRelaySettings()
- const { pubkey, relayList, followings } = useNostr()
- const urls = useMemo(() => {
- return feedType === 'following'
- ? relayList?.read.length
- ? relayList.read.slice(0, 4)
- : BIG_RELAY_URLS
- : temporaryRelayUrls.length > 0
- ? temporaryRelayUrls
- : relayUrls
- }, [feedType, relayUrls, relayList, temporaryRelayUrls])
+ const { feedType, relayUrls, isReady, filter } = useFeed()
useEffect(() => {
if (layoutRef.current) {
@@ -38,20 +24,8 @@ export default function NoteListPage() {
titlebar={
}
displayScrollToTopButton
>
- {!!urls.length && (feedType === 'relays' || (relayList && followings)) ? (
-
+ {isReady ? (
+
) : (
{t('loading...')}
)}
diff --git a/src/pages/secondary/NoteListPage/index.tsx b/src/pages/secondary/NoteListPage/index.tsx
index 807b635..742bc58 100644
--- a/src/pages/secondary/NoteListPage/index.tsx
+++ b/src/pages/secondary/NoteListPage/index.tsx
@@ -1,16 +1,17 @@
import NoteList from '@/components/NoteList'
import { SEARCHABLE_RELAY_URLS } from '@/constants'
-import { useSearchParams } from '@/hooks'
+import { useFetchRelayInfos, useSearchParams } from '@/hooks'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { isWebsocketUrl, simplifyUrl } from '@/lib/url'
-import { useRelaySettings } from '@/providers/RelaySettingsProvider'
+import { useFeed } from '@/providers/FeedProvider'
import { Filter } from 'nostr-tools'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
export default function NoteListPage({ index }: { index?: number }) {
const { t } = useTranslation()
- const { relayUrls, searchableRelayUrls } = useRelaySettings()
+ const { relayUrls } = useFeed()
+ const { searchableRelayUrls } = useFetchRelayInfos(relayUrls)
const { searchParams } = useSearchParams()
const relayUrlsString = JSON.stringify(relayUrls)
const {
@@ -31,10 +32,7 @@ export default function NoteListPage({ index }: { index?: number }) {
return {
title: `${t('Search')}: ${search}`,
filter: { search },
- urls:
- searchableRelayUrls.length < 4
- ? searchableRelayUrls.concat(SEARCHABLE_RELAY_URLS).slice(0, 4)
- : searchableRelayUrls
+ urls: searchableRelayUrls.concat(SEARCHABLE_RELAY_URLS).slice(0, 4)
}
}
const relayUrl = searchParams.get('relay')
@@ -44,16 +42,6 @@ export default function NoteListPage({ index }: { index?: number }) {
return { urls: relayUrls }
}, [searchParams, relayUrlsString])
- if (filter?.search && searchableRelayUrls.length === 0) {
- return (
-
-
- {t('The relays you are connected to do not support search')}
-
-
- )
- }
-
return (
diff --git a/src/pages/secondary/ProfileListPage/index.tsx b/src/pages/secondary/ProfileListPage/index.tsx
index 1ce0906..d15eea6 100644
--- a/src/pages/secondary/ProfileListPage/index.tsx
+++ b/src/pages/secondary/ProfileListPage/index.tsx
@@ -1,7 +1,8 @@
import UserItem from '@/components/UserItem'
-import { useSearchParams } from '@/hooks'
+import { SEARCHABLE_RELAY_URLS } from '@/constants'
+import { useFetchRelayInfos, useSearchParams } from '@/hooks'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
-import { useRelaySettings } from '@/providers/RelaySettingsProvider'
+import { useFeed } from '@/providers/FeedProvider'
import client from '@/services/client.service'
import dayjs from 'dayjs'
import { Filter } from 'nostr-tools'
@@ -13,7 +14,8 @@ const LIMIT = 50
export default function ProfileListPage({ index }: { index?: number }) {
const { t } = useTranslation()
const { searchParams } = useSearchParams()
- const { relayUrls, searchableRelayUrls } = useRelaySettings()
+ const { relayUrls } = useFeed()
+ const { searchableRelayUrls } = useFetchRelayInfos(relayUrls)
const [until, setUntil] = useState(() => dayjs().unix())
const [hasMore, setHasMore] = useState(true)
const [pubkeySet, setPubkeySet] = useState(new Set())
@@ -27,7 +29,7 @@ export default function ProfileListPage({ index }: { index?: number }) {
return f
}, [searchParams, until])
const urls = useMemo(() => {
- return filter.search ? searchableRelayUrls : relayUrls
+ return filter.search ? searchableRelayUrls.concat(SEARCHABLE_RELAY_URLS).slice(0, 4) : relayUrls
}, [relayUrls, searchableRelayUrls, filter])
const title = useMemo(() => {
return filter.search ? `${t('Search')}: ${filter.search}` : t('All users')
diff --git a/src/pages/secondary/ProfilePage/index.tsx b/src/pages/secondary/ProfilePage/index.tsx
index a3e732d..1d98de4 100644
--- a/src/pages/secondary/ProfilePage/index.tsx
+++ b/src/pages/secondary/ProfilePage/index.tsx
@@ -13,9 +13,9 @@ import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { toFollowingList } from '@/lib/link'
import { generateImageByPubkey } from '@/lib/pubkey'
import { SecondaryPageLink } from '@/PageManager'
+import { useFeed } from '@/providers/FeedProvider'
import { useFollowList } from '@/providers/FollowListProvider'
import { useNostr } from '@/providers/NostrProvider'
-import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import NotFoundPage from '../NotFoundPage'
@@ -24,9 +24,12 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number
const { t } = useTranslation()
const { profile, isFetching } = useFetchProfile(id)
const { relayList, isFetching: isFetchingRelayInfo } = useFetchRelayList(profile?.pubkey)
- const { relayUrls: currentRelayUrls } = useRelaySettings()
+ const { relayUrls: currentRelayUrls } = useFeed()
const relayUrls = useMemo(
- () => relayList.write.slice(0, 4).concat(currentRelayUrls.slice(0, 1)),
+ () =>
+ relayList.write.length < 4
+ ? relayList.write.concat(currentRelayUrls).slice(0, 4)
+ : relayList.write.slice(0, 4),
[relayList, currentRelayUrls]
)
const { pubkey: accountPubkey } = useNostr()
diff --git a/src/pages/secondary/RelaySettingsPage/index.tsx b/src/pages/secondary/RelaySettingsPage/index.tsx
index 9aa5421..3d35eb3 100644
--- a/src/pages/secondary/RelaySettingsPage/index.tsx
+++ b/src/pages/secondary/RelaySettingsPage/index.tsx
@@ -1,4 +1,4 @@
-import RelaySettings from '@/components/RelaySettings'
+import RelaySetsSetting from '@/components/RelaySetsSetting'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { useTranslation } from 'react-i18next'
@@ -8,7 +8,7 @@ export default function RelaySettingsPage({ index }: { index?: number }) {
return (
-
+
)
diff --git a/src/providers/FeedProvider.tsx b/src/providers/FeedProvider.tsx
index 5eb955c..b2b5753 100644
--- a/src/providers/FeedProvider.tsx
+++ b/src/providers/FeedProvider.tsx
@@ -1,9 +1,20 @@
+import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
+import client from '@/services/client.service'
+import storage from '@/services/storage.service'
import { TFeedType } from '@/types'
-import { createContext, useContext, useState } from 'react'
+import { Filter } from 'nostr-tools'
+import { createContext, useContext, useEffect, useState } from 'react'
+import { useNostr } from './NostrProvider'
+import { useRelaySets } from './RelaySetsProvider'
type TFeedContext = {
feedType: TFeedType
- setFeedType: (feedType: TFeedType) => void
+ relayUrls: string[]
+ temporaryRelayUrls: string[]
+ filter: Filter
+ isReady: boolean
+ activeRelaySetId: string | null
+ switchFeed: (feedType: TFeedType, options?: { activeRelaySetId?: string }) => Promise
}
const FeedContext = createContext(undefined)
@@ -17,7 +28,113 @@ export const useFeed = () => {
}
export function FeedProvider({ children }: { children: React.ReactNode }) {
+ const { pubkey } = useNostr()
+ const { relaySets } = useRelaySets()
const [feedType, setFeedType] = useState('relays')
+ const [relayUrls, setRelayUrls] = useState([])
+ const [temporaryRelayUrls, setTemporaryRelayUrls] = useState([])
+ const [filter, setFilter] = useState({})
+ const [isReady, setIsReady] = useState(false)
+ const [activeRelaySetId, setActiveRelaySetId] = useState(() =>
+ storage.getActiveRelaySetId()
+ )
- return {children}
+ useEffect(() => {
+ const init = async () => {
+ // temporary relay urls from query params
+ const searchParams = new URLSearchParams(window.location.search)
+ const tempRelays = searchParams
+ .getAll('r')
+ .map((url) =>
+ !url.startsWith('ws://') && !url.startsWith('wss://') ? `wss://${url}` : url
+ )
+ .filter((url) => isWebsocketUrl(url))
+ .map((url) => normalizeUrl(url))
+ if (tempRelays.length) {
+ setTemporaryRelayUrls(tempRelays)
+ return await switchFeed('temporary')
+ }
+
+ await switchFeed('relays', { activeRelaySetId })
+ }
+
+ init()
+ }, [])
+
+ useEffect(() => {
+ if (feedType !== 'following') return
+
+ switchFeed('following')
+ }, [pubkey])
+
+ useEffect(() => {
+ if (feedType !== 'relays') return
+
+ const relaySet = relaySets.find((set) => set.id === activeRelaySetId)
+ if (!relaySet) return
+
+ setRelayUrls(relaySet.relayUrls)
+ }, [relaySets])
+
+ const switchFeed = async (
+ feedType: TFeedType,
+ options: { activeRelaySetId?: string | null } = {}
+ ) => {
+ setIsReady(false)
+ if (feedType === 'relays') {
+ const relaySetId = options.activeRelaySetId ?? (relaySets.length > 0 ? relaySets[0].id : null)
+ if (!relaySetId) return
+
+ const relaySet =
+ relaySets.find((set) => set.id === options.activeRelaySetId) ??
+ (relaySets.length > 0 ? relaySets[0] : null)
+ if (relaySet) {
+ setFeedType(feedType)
+ setRelayUrls(relaySet.relayUrls)
+ setActiveRelaySetId(relaySet.id)
+ setFilter({})
+ setIsReady(true)
+ storage.setActiveRelaySetId(relaySet.id)
+ }
+ return
+ }
+ if (feedType === 'following') {
+ if (!pubkey) return
+ setFeedType(feedType)
+ setActiveRelaySetId(null)
+ const [relayList, followings] = await Promise.all([
+ client.fetchRelayList(pubkey),
+ client.fetchFollowings(pubkey)
+ ])
+ setRelayUrls(relayList.read.slice(0, 4))
+ setFilter({ authors: followings.includes(pubkey) ? followings : [...followings, pubkey] })
+ setIsReady(true)
+ return
+ }
+ if (feedType === 'temporary') {
+ setFeedType(feedType)
+ setRelayUrls(temporaryRelayUrls)
+ setActiveRelaySetId(null)
+ setFilter({})
+ setIsReady(true)
+ return
+ }
+ setIsReady(true)
+ }
+
+ return (
+
+ {children}
+
+ )
}
diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx
index 0eaed72..90cb9e5 100644
--- a/src/providers/NostrProvider/index.tsx
+++ b/src/providers/NostrProvider/index.tsx
@@ -7,7 +7,6 @@ import { ISigner, TAccount, TAccountPointer, TDraftEvent, TRelayList } from '@/t
import dayjs from 'dayjs'
import { Event, kinds } from 'nostr-tools'
import { createContext, useContext, useEffect, useState } from 'react'
-import { useRelaySettings } from '../RelaySettingsProvider'
import { BunkerSigner } from './bunker.signer'
import { Nip07Signer } from './nip-07.signer'
import { NsecSigner } from './nsec.signer'
@@ -47,7 +46,6 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
const [account, setAccount] = useState(null)
const [signer, setSigner] = useState(null)
const [openLoginDialog, setOpenLoginDialog] = useState(false)
- const { relayUrls: currentRelayUrls } = useRelaySettings()
const { relayList, isFetching: isFetchingRelayList } = useFetchRelayList(account?.pubkey)
const { followings, isFetching: isFetchingFollowings } = useFetchFollowings(account?.pubkey)
@@ -196,10 +194,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
const publish = async (draftEvent: TDraftEvent, additionalRelayUrls: string[] = []) => {
const event = await signEvent(draftEvent)
- await client.publishEvent(
- relayList.write.concat(additionalRelayUrls).concat(currentRelayUrls),
- event
- )
+ await client.publishEvent(relayList.write.concat(additionalRelayUrls), event)
return event
}
diff --git a/src/providers/RelaySetsProvider.tsx b/src/providers/RelaySetsProvider.tsx
new file mode 100644
index 0000000..48718da
--- /dev/null
+++ b/src/providers/RelaySetsProvider.tsx
@@ -0,0 +1,80 @@
+import { randomString } from '@/lib/random'
+import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
+import storage from '@/services/storage.service'
+import { TRelaySet } from '@/types'
+import { createContext, useContext, useEffect, useState } from 'react'
+
+type TRelaySetsContext = {
+ relaySets: TRelaySet[]
+ addRelaySet: (relaySetName: string, relayUrls?: string[]) => string
+ deleteRelaySet: (id: string) => void
+ updateRelaySet: (newSet: TRelaySet) => void
+ mergeRelaySets: (newSets: TRelaySet[]) => void
+}
+
+const RelaySetsContext = createContext(undefined)
+
+export const useRelaySets = () => {
+ const context = useContext(RelaySetsContext)
+ if (!context) {
+ throw new Error('useRelaySets must be used within a RelaySetsProvider')
+ }
+ return context
+}
+
+export function RelaySetsProvider({ children }: { children: React.ReactNode }) {
+ const [relaySets, setRelaySets] = useState(() => storage.getRelaySets())
+
+ useEffect(() => {
+ storage.setRelaySets(relaySets)
+ }, [relaySets])
+
+ const deleteRelaySet = (id: string) => {
+ setRelaySets((pre) => pre.filter((set) => set.id !== id))
+ }
+
+ const updateRelaySet = (newSet: TRelaySet) => {
+ setRelaySets((pre) => {
+ return pre.map((set) => (set.id === newSet.id ? newSet : set))
+ })
+ }
+
+ const addRelaySet = (relaySetName: string, relayUrls: string[] = []) => {
+ const normalizedUrls = relayUrls
+ .filter((url) => isWebsocketUrl(url))
+ .map((url) => normalizeUrl(url))
+ const id = randomString()
+ setRelaySets((pre) => {
+ return [
+ ...pre,
+ {
+ id,
+ name: relaySetName,
+ relayUrls: normalizedUrls
+ }
+ ]
+ })
+ return id
+ }
+
+ const mergeRelaySets = (newSets: TRelaySet[]) => {
+ setRelaySets((pre) => {
+ const newIds = newSets.map((set) => set.id)
+ return pre.filter((set) => !newIds.includes(set.id)).concat(newSets)
+ })
+ }
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/providers/RelaySettingsProvider.tsx b/src/providers/RelaySettingsProvider.tsx
deleted file mode 100644
index 7b5fbd4..0000000
--- a/src/providers/RelaySettingsProvider.tsx
+++ /dev/null
@@ -1,178 +0,0 @@
-import { SEARCHABLE_RELAY_URLS } from '@/constants'
-import { checkAlgoRelay, checkSearchRelay } from '@/lib/relay'
-import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
-import client from '@/services/client.service'
-import storage from '@/services/storage.service'
-import { TRelayGroup } from '@/types'
-import { createContext, Dispatch, useContext, useEffect, useState } from 'react'
-import { useFeed } from './FeedProvider'
-
-type TRelaySettingsContext = {
- relayGroups: TRelayGroup[]
- temporaryRelayUrls: string[]
- relayUrls: string[]
- searchableRelayUrls: string[]
- areAlgoRelays: boolean
- switchRelayGroup: (groupName: string) => void
- renameRelayGroup: (oldGroupName: string, newGroupName: string) => string | null
- deleteRelayGroup: (groupName: string) => void
- addRelayGroup: (groupName: string, relayUrls?: string[]) => string | null
- updateRelayGroupRelayUrls: (groupName: string, relayUrls: string[]) => void
- setTemporaryRelayUrls: Dispatch
-}
-
-const RelaySettingsContext = createContext(undefined)
-
-export const useRelaySettings = () => {
- const context = useContext(RelaySettingsContext)
- if (!context) {
- throw new Error('useRelaySettings must be used within a RelaySettingsProvider')
- }
- return context
-}
-
-export function RelaySettingsProvider({ children }: { children: React.ReactNode }) {
- const { setFeedType } = useFeed()
- const [relayGroups, setRelayGroups] = useState([])
- const [temporaryRelayUrls, setTemporaryRelayUrls] = useState([])
- const [relayUrls, setRelayUrls] = useState(
- temporaryRelayUrls.length
- ? temporaryRelayUrls
- : (relayGroups.find((group) => group.isActive)?.relayUrls ?? [])
- )
- const [searchableRelayUrls, setSearchableRelayUrls] = useState(SEARCHABLE_RELAY_URLS)
- const [areAlgoRelays, setAreAlgoRelays] = useState(false)
-
- useEffect(() => {
- const searchParams = new URLSearchParams(window.location.search)
- const tempRelays = searchParams
- .getAll('r')
- .map((url) => (url.startsWith('wss://') || url.startsWith('ws://') ? url : `wss://${url}`))
- .filter((url) => isWebsocketUrl(url))
- .map((url) => normalizeUrl(url))
- if (tempRelays.length) {
- setTemporaryRelayUrls(tempRelays)
- setFeedType('relays')
- }
- const storedGroups = storage.getRelayGroups()
- setRelayGroups(storedGroups)
- }, [])
-
- useEffect(() => {
- const handler = async () => {
- const newRelayUrls = temporaryRelayUrls.length
- ? temporaryRelayUrls
- : (relayGroups.find((group) => group.isActive)?.relayUrls ?? [])
-
- if (JSON.stringify(relayUrls) !== JSON.stringify(newRelayUrls)) {
- setRelayUrls(newRelayUrls)
- }
- const relayInfos = await client.fetchRelayInfos(newRelayUrls)
- const searchableRelayUrls = newRelayUrls.filter((_, index) =>
- checkSearchRelay(relayInfos[index])
- )
- setSearchableRelayUrls(
- searchableRelayUrls.length ? searchableRelayUrls : SEARCHABLE_RELAY_URLS
- )
- const nonAlgoRelayUrls = newRelayUrls.filter((_, index) => !checkAlgoRelay(relayInfos[index]))
- setAreAlgoRelays(newRelayUrls.length > 0 && nonAlgoRelayUrls.length === 0)
- client.setCurrentRelayUrls(nonAlgoRelayUrls)
- }
- handler()
- }, [relayGroups, temporaryRelayUrls, relayUrls])
-
- const updateGroups = (fn: (pre: TRelayGroup[]) => TRelayGroup[]) => {
- let newGroups = relayGroups
- setRelayGroups((pre) => {
- newGroups = fn(pre)
- return newGroups
- })
- storage.setRelayGroups(newGroups)
- }
-
- const switchRelayGroup = (groupName: string) => {
- updateGroups((pre) =>
- pre.map((group) => ({
- ...group,
- isActive: group.groupName === groupName
- }))
- )
- setFeedType('relays')
- setTemporaryRelayUrls([])
- }
-
- const deleteRelayGroup = (groupName: string) => {
- updateGroups((pre) => pre.filter((group) => group.groupName !== groupName))
- }
-
- const updateRelayGroupRelayUrls = (groupName: string, relayUrls: string[]) => {
- updateGroups((pre) =>
- pre.map((group) => ({
- ...group,
- relayUrls: group.groupName === groupName ? relayUrls : group.relayUrls
- }))
- )
- }
-
- const renameRelayGroup = (oldGroupName: string, newGroupName: string) => {
- if (newGroupName === '') {
- return null
- }
- if (oldGroupName === newGroupName) {
- return null
- }
- updateGroups((pre) => {
- if (pre.some((group) => group.groupName === newGroupName)) {
- return pre
- }
- return pre.map((group) => ({
- ...group,
- groupName: group.groupName === oldGroupName ? newGroupName : group.groupName
- }))
- })
- return null
- }
-
- const addRelayGroup = (groupName: string, relayUrls: string[] = []) => {
- if (groupName === '') {
- return null
- }
- const normalizedUrls = relayUrls
- .filter((url) => isWebsocketUrl(url))
- .map((url) => normalizeUrl(url))
- updateGroups((pre) => {
- if (pre.some((group) => group.groupName === groupName)) {
- return pre
- }
- return [
- ...pre,
- {
- groupName,
- relayUrls: normalizedUrls,
- isActive: false
- }
- ]
- })
- return null
- }
-
- return (
-
- {children}
-
- )
-}
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index 9355db4..0b48dcb 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -1,4 +1,5 @@
import { BIG_RELAY_URLS } from '@/constants'
+import { getFollowingsFromFollowListEvent } from '@/lib/event'
import { formatPubkey } from '@/lib/pubkey'
import { tagNameEquals } from '@/lib/tag'
import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
@@ -100,7 +101,9 @@ class ClientService extends EventTarget {
}
async publishEvent(relayUrls: string[], event: NEvent) {
- const result = await Promise.any(this.pool.publish(relayUrls, event))
+ const result = await Promise.any(
+ this.pool.publish(relayUrls.concat(this.defaultRelayUrls), event)
+ )
this.dispatchEvent(new CustomEvent('eventPublished', { detail: event }))
return result
}
@@ -417,6 +420,11 @@ class ClientService extends EventTarget {
return this.followListCache.fetch(pubkey)
}
+ async fetchFollowings(pubkey: string) {
+ const followListEvent = await this.fetchFollowListEvent(pubkey)
+ return followListEvent ? getFollowingsFromFollowListEvent(followListEvent) : []
+ }
+
updateFollowListCache(pubkey: string, event: NEvent) {
this.followListCache.set(pubkey, Promise.resolve(event))
}
diff --git a/src/services/storage.service.ts b/src/services/storage.service.ts
index a1ddfc3..e951ef9 100644
--- a/src/services/storage.service.ts
+++ b/src/services/storage.service.ts
@@ -1,19 +1,26 @@
import { StorageKey } from '@/constants'
import { isSameAccount } from '@/lib/account'
-import { TAccount, TRelayGroup, TAccountPointer, TThemeSetting } from '@/types'
+import { randomString } from '@/lib/random'
+import { TAccount, TAccountPointer, TRelaySet, TThemeSetting } from '@/types'
-const DEFAULT_RELAY_GROUPS: TRelayGroup[] = [
+const DEFAULT_RELAY_SETS: TRelaySet[] = [
{
- groupName: 'Global',
- relayUrls: ['wss://relay.damus.io/', 'wss://nos.lol/'],
- isActive: true
+ id: randomString(),
+ name: 'Global',
+ relayUrls: ['wss://relay.damus.io/', 'wss://nos.lol/']
+ },
+ {
+ id: randomString(),
+ name: 'Algo',
+ relayUrls: ['wss://algo.utxo.one']
}
]
class StorageService {
static instance: StorageService
- private relayGroups: TRelayGroup[] = []
+ private relaySets: TRelaySet[] = []
+ private activeRelaySetId: string | null = null
private themeSetting: TThemeSetting = 'system'
private accounts: TAccount[] = []
private currentAccount: TAccount | null = null
@@ -27,23 +34,64 @@ class StorageService {
}
init() {
- const relayGroupsStr = window.localStorage.getItem(StorageKey.RELAY_GROUPS)
- this.relayGroups = relayGroupsStr ? JSON.parse(relayGroupsStr) : DEFAULT_RELAY_GROUPS
this.themeSetting =
(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
+
+ const relaySetsStr = window.localStorage.getItem(StorageKey.RELAY_SETS)
+ if (!relaySetsStr) {
+ let relaySets: TRelaySet[] = []
+ const legacyRelayGroupsStr = window.localStorage.getItem('relayGroups')
+ if (legacyRelayGroupsStr) {
+ const legacyRelayGroups = JSON.parse(legacyRelayGroupsStr)
+ relaySets = legacyRelayGroups.map((group: any) => {
+ return {
+ id: randomString(),
+ name: group.groupName,
+ relayUrls: group.relayUrls
+ }
+ })
+ }
+ if (!relaySets.length) {
+ relaySets = DEFAULT_RELAY_SETS
+ }
+ const activeRelaySetId = relaySets[0].id
+ window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(relaySets))
+ window.localStorage.setItem(StorageKey.ACTIVE_RELAY_SET_ID, activeRelaySetId)
+ this.relaySets = relaySets
+ this.activeRelaySetId = activeRelaySetId
+ } else {
+ this.relaySets = JSON.parse(relaySetsStr)
+ this.activeRelaySetId = window.localStorage.getItem(StorageKey.ACTIVE_RELAY_SET_ID) ?? null
+ }
+ }
+
+ getRelaySets() {
+ return this.relaySets
+ }
+
+ setRelaySets(relaySets: TRelaySet[]) {
+ this.relaySets = relaySets
+ window.localStorage.setItem(StorageKey.RELAY_SETS, JSON.stringify(this.relaySets))
}
- getRelayGroups() {
- return this.relayGroups
+ getActiveRelaySetId() {
+ return this.activeRelaySetId
}
- setRelayGroups(relayGroups: TRelayGroup[]) {
- window.localStorage.setItem(StorageKey.RELAY_GROUPS, JSON.stringify(relayGroups))
- this.relayGroups = relayGroups
+ setActiveRelaySetId(id: string | null) {
+ this.activeRelaySetId = id
+ if (id) {
+ window.localStorage.setItem(
+ StorageKey.ACTIVE_RELAY_SET_ID,
+ JSON.stringify(this.activeRelaySetId)
+ )
+ } else {
+ window.localStorage.removeItem(StorageKey.ACTIVE_RELAY_SET_ID)
+ }
}
getThemeSetting() {
diff --git a/src/types.ts b/src/types.ts
index dc2bf8c..7c7d169 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -26,14 +26,14 @@ export type TWebMetadata = {
image?: string | null
}
-export type TRelayGroup = {
- groupName: string
+export type TRelaySet = {
+ id: string
+ name: string
relayUrls: string[]
- isActive: boolean
}
export type TConfig = {
- relayGroups: TRelayGroup[]
+ relayGroups: TRelaySet[]
theme: TThemeSetting
}
@@ -64,6 +64,6 @@ export type TAccount = {
export type TAccountPointer = Pick
-export type TFeedType = 'following' | 'relays'
+export type TFeedType = 'following' | 'relays' | 'temporary'
export type TLanguage = 'en' | 'zh'