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} ) }