diff --git a/src/components/Embedded/EmbeddedNote.tsx b/src/components/Embedded/EmbeddedNote.tsx index f29e851..67d6448 100644 --- a/src/components/Embedded/EmbeddedNote.tsx +++ b/src/components/Embedded/EmbeddedNote.tsx @@ -1,5 +1,6 @@ import { Skeleton } from '@/components/ui/skeleton' import { useFetchEvent } from '@/hooks' +import { normalizeUrl } from '@/lib/url' import { cn } from '@/lib/utils' import client from '@/services/client.service' import { useTranslation } from 'react-i18next' @@ -95,7 +96,7 @@ function EmbeddedNoteNotFound({ // Calculate which external relays would be tried useEffect(() => { const getExternalRelays = async () => { - const relays: string[] = [] + let relays: string[] = [] if (!/^[0-9a-f]{64}$/.test(noteId)) { try { @@ -112,6 +113,9 @@ function EmbeddedNoteNotFound({ const authorRelayList = await client.fetchRelayList(data.pubkey) relays.push(...authorRelayList.write.slice(0, 6)) } + // Normalize and deduplicate relays + relays = relays.map(url => normalizeUrl(url) || url) + relays = Array.from(new Set(relays)) } catch (err) { console.error('Failed to parse external relays:', err) } @@ -120,7 +124,9 @@ function EmbeddedNoteNotFound({ const seenOn = client.getSeenEventRelayUrls(noteId) relays.push(...seenOn) - setExternalRelays(Array.from(new Set(relays))) + // Normalize and deduplicate final relay list + const normalizedRelays = relays.map(url => normalizeUrl(url) || url) + setExternalRelays(Array.from(new Set(normalizedRelays))) } getExternalRelays() diff --git a/src/components/NormalFeed/index.tsx b/src/components/NormalFeed/index.tsx index 734b376..7efd726 100644 --- a/src/components/NormalFeed/index.tsx +++ b/src/components/NormalFeed/index.tsx @@ -5,39 +5,44 @@ import { useKindFilter } from '@/providers/KindFilterProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import storage from '@/services/local-storage.service' import { TFeedSubRequest, TNoteListMode } from '@/types' -import { useMemo, useRef, useState } from 'react' +import { forwardRef, useMemo, useRef, useState } from 'react' import KindFilter from '../KindFilter' import { RefreshButton } from '../RefreshButton' -export default function NormalFeed({ - subRequests, - areAlgoRelays = false, - isMainFeed = false, - showRelayCloseReason = false -}: { +const NormalFeed = forwardRef(function NormalFeed({ + subRequests, + areAlgoRelays = false, + isMainFeed = false, + showRelayCloseReason = false +}, ref) { const { hideUntrustedNotes } = useUserTrust() const { showKinds } = useKindFilter() const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) const [listMode, setListMode] = useState(() => storage.getNoteListMode()) const supportTouch = useMemo(() => isTouchDevice(), []) - const noteListRef = useRef(null) + const internalNoteListRef = useRef(null) + const noteListRef = ref || internalNoteListRef const handleListModeChange = (mode: TNoteListMode) => { setListMode(mode) if (isMainFeed) { storage.setNoteListMode(mode) } - noteListRef.current?.scrollToTop('smooth') + if (noteListRef && typeof noteListRef !== 'function') { + noteListRef.current?.scrollToTop('smooth') + } } const handleShowKindsChange = (newShowKinds: number[]) => { setTemporaryShowKinds(newShowKinds) - noteListRef.current?.scrollToTop() + if (noteListRef && typeof noteListRef !== 'function') { + noteListRef.current?.scrollToTop() + } } return ( @@ -53,7 +58,11 @@ export default function NormalFeed({ }} options={ <> - {!supportTouch && noteListRef.current?.refresh()} />} + {!supportTouch && { + if (noteListRef && typeof noteListRef !== 'function') { + noteListRef.current?.refresh() + } + }} />} } @@ -69,4 +78,6 @@ export default function NormalFeed({ /> ) -} +}) + +export default NormalFeed diff --git a/src/components/NoteOptions/useMenuActions.tsx b/src/components/NoteOptions/useMenuActions.tsx index 9760282..21bbcf3 100644 --- a/src/components/NoteOptions/useMenuActions.tsx +++ b/src/components/NoteOptions/useMenuActions.tsx @@ -2,7 +2,7 @@ import { ExtendedKind } from '@/constants' import { getNoteBech32Id, isProtectedEvent, getRootEventHexId } from '@/lib/event' import { toNjump } from '@/lib/link' import { pubkeyToNpub } from '@/lib/pubkey' -import { simplifyUrl } from '@/lib/url' +import { normalizeUrl, simplifyUrl } from '@/lib/url' import { useCurrentRelays } from '@/providers/CurrentRelaysProvider' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useMuteList } from '@/providers/MuteListProvider' @@ -53,7 +53,10 @@ export function useMenuActions({ const { relayUrls: currentBrowsingRelayUrls } = useCurrentRelays() const { relaySets, favoriteRelays } = useFavoriteRelays() const relayUrls = useMemo(() => { - return Array.from(new Set(currentBrowsingRelayUrls.concat(favoriteRelays))) + return Array.from(new Set([ + ...currentBrowsingRelayUrls.map(url => normalizeUrl(url) || url), + ...favoriteRelays.map(url => normalizeUrl(url) || url) + ])) }, [currentBrowsingRelayUrls, favoriteRelays]) const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeySet } = useMuteList() const isMuted = useMemo(() => mutePubkeySet.has(event.pubkey), [mutePubkeySet, event]) diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx index ae6ac20..8d007aa 100644 --- a/src/components/PostEditor/PostContent.tsx +++ b/src/components/PostEditor/PostContent.tsx @@ -13,7 +13,9 @@ import { import { ExtendedKind } from '@/constants' import { isTouchDevice } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' +import { useFeed } from '@/providers/FeedProvider' import { useReply } from '@/providers/ReplyProvider' +import { normalizeUrl } from '@/lib/url' import postEditorCache from '@/services/post-editor-cache.service' import { TPollCreateData } from '@/types' import { ImageUp, ListTodo, LoaderCircle, MessageCircle, Settings, Smile, X, Highlighter } from 'lucide-react' @@ -43,6 +45,7 @@ export default function PostContent({ }) { const { t } = useTranslation() const { pubkey, publish, checkLogin } = useNostr() + const { feedInfo } = useFeed() const { addReplies } = useReply() const [text, setText] = useState('') const textareaRef = useRef(null) @@ -59,6 +62,7 @@ export default function PostContent({ const [publicMessageRecipients, setPublicMessageRecipients] = useState([]) const [isProtectedEvent, setIsProtectedEvent] = useState(false) const [additionalRelayUrls, setAdditionalRelayUrls] = useState([]) + const [userWriteRelays, setUserWriteRelays] = useState([]) const [isHighlight, setIsHighlight] = useState(false) const [highlightData, setHighlightData] = useState({ sourceType: 'nostr', @@ -243,12 +247,30 @@ export default function PostContent({ // console.log('Publishing draft event:', draftEvent) const newEvent = await publish(draftEvent, { - specifiedRelayUrls: isProtectedEvent ? additionalRelayUrls : undefined, + specifiedRelayUrls: isProtectedEvent ? additionalRelayUrls.filter(url => !userWriteRelays.includes(url)) : undefined, additionalRelayUrls: isPoll ? pollCreateData.relays : additionalRelayUrls, minPow }) // console.log('Published event:', newEvent) + // Check if we need to refresh the current relay view + if (feedInfo.feedType === 'relay' && feedInfo.id) { + const currentRelayUrl = normalizeUrl(feedInfo.id) + const publishedRelays = isProtectedEvent + ? additionalRelayUrls.filter(url => !userWriteRelays.includes(url)) + : additionalRelayUrls + + // If we published to the current relay being viewed, trigger a refresh after a short delay + if (publishedRelays.some(url => normalizeUrl(url) === currentRelayUrl)) { + setTimeout(() => { + // Trigger a page refresh by dispatching a custom event that the relay view can listen to + window.dispatchEvent(new CustomEvent('relay-refresh-needed', { + detail: { relayUrl: currentRelayUrl } + })) + }, 1000) // 1 second delay to allow the event to propagate + } + } + // Show publishing feedback if ((newEvent as any).relayStatuses) { showPublishingFeedback({ @@ -470,6 +492,7 @@ export default function PostContent({ > setAdditionalRelayUrls: Dispatch> + setUserWriteRelays?: Dispatch> content?: string }) { const { t } = useTranslation() @@ -52,13 +54,15 @@ export default function PostRelaySelector({ // Get all selectable relays (write relays + favorite relays + relays from relay sets + mention relays) const selectableRelays = useMemo(() => { - const allRelays = Array.from(new Set([ - ...relayUrls, - ...favoriteRelays, - ...relaySets.flatMap(set => set.relayUrls), - ...mentionRelays - ])) - return allRelays + // Normalize all relay URLs before combining them + const normalizedRelays = [ + ...relayUrls.map(url => normalizeUrl(url) || url), + ...favoriteRelays.map(url => normalizeUrl(url) || url), + ...relaySets.flatMap(set => set.relayUrls.map(url => normalizeUrl(url) || url)), + ...mentionRelays.map(url => normalizeUrl(url) || url) + ].filter(Boolean) // Remove any null/undefined values + + return Array.from(new Set(normalizedRelays)) }, [relayUrls, favoriteRelays, relaySets, mentionRelays]) const description = useMemo(() => { @@ -171,7 +175,10 @@ export default function PostRelaySelector({ // Default to write relays + mention relays for regular replies, or just write relays for other cases if (isRegularReply) { // For regular replies, include write relays and mention relays - const defaultRelays = Array.from(new Set([...relayUrls, ...mentionRelays])) + // Normalize URLs before combining to avoid duplicates with/without trailing slashes + const normalizedWriteRelays = relayUrls.map(url => normalizeUrl(url) || url) + const normalizedMentionRelays = mentionRelays.map(url => normalizeUrl(url) || url) + const defaultRelays = Array.from(new Set([...normalizedWriteRelays, ...normalizedMentionRelays])) console.log('PostRelaySelector: Setting default relays for regular reply:', { relayUrls, mentionRelays, @@ -191,7 +198,9 @@ export default function PostRelaySelector({ const isProtectedEvent = selectedRelayUrls.length > 0 && !selectedRelayUrls.some(url => relayUrls.includes(url)) setIsProtectedEvent(isProtectedEvent) setAdditionalRelayUrls(selectedRelayUrls) - }, [selectedRelayUrls, relayUrls, setIsProtectedEvent, setAdditionalRelayUrls]) + // Expose user's write relays to parent component + setUserWriteRelays?.(relayUrls) + }, [selectedRelayUrls, relayUrls, setIsProtectedEvent, setAdditionalRelayUrls, setUserWriteRelays]) const handleRelayCheckedChange = useCallback((checked: boolean, url: string) => { if (checked) { diff --git a/src/components/QuoteList/index.tsx b/src/components/QuoteList/index.tsx index acf62e9..4def028 100644 --- a/src/components/QuoteList/index.tsx +++ b/src/components/QuoteList/index.tsx @@ -1,5 +1,6 @@ import { FAST_READ_RELAY_URLS, ExtendedKind } from '@/constants' import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event' +import { normalizeUrl } from '@/lib/url' import { useNostr } from '@/providers/NostrProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import client from '@/services/client.service' @@ -31,7 +32,10 @@ export default function QuoteList({ event, className }: { event: Event; classNam // Privacy: Only use user's own relays + defaults, never connect to other users' relays const userRelays = userRelayList?.read || [] - const finalRelayUrls = Array.from(new Set(userRelays.concat(FAST_READ_RELAY_URLS))) + const finalRelayUrls = Array.from(new Set([ + ...userRelays.map(url => normalizeUrl(url) || url), + ...FAST_READ_RELAY_URLS.map(url => normalizeUrl(url) || url) + ])) const { closer, timelineKey } = await client.subscribeTimeline( [ diff --git a/src/components/Relay/index.tsx b/src/components/Relay/index.tsx index 6e6005e..1522aa4 100644 --- a/src/components/Relay/index.tsx +++ b/src/components/Relay/index.tsx @@ -4,8 +4,9 @@ import SearchInput from '@/components/SearchInput' import { useFetchRelayInfo } from '@/hooks' import { normalizeUrl } from '@/lib/url' import { useCurrentRelays } from '@/providers/CurrentRelaysProvider' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { TNoteListRef } from '@/components/NoteList' import NotFound from '../NotFound' export default function Relay({ url, className }: { url?: string; className?: string }) { @@ -15,6 +16,7 @@ export default function Relay({ url, className }: { url?: string; className?: st const { relayInfo } = useFetchRelayInfo(normalizedUrl) const [searchInput, setSearchInput] = useState('') const [debouncedInput, setDebouncedInput] = useState(searchInput) + const noteListRef = useRef(null) useEffect(() => { if (normalizedUrl) { @@ -35,6 +37,25 @@ export default function Relay({ url, className }: { url?: string; className?: st } }, [searchInput]) + // Listen for refresh events when user publishes to this relay + useEffect(() => { + if (!normalizedUrl) return + + const handleRelayRefresh = (event: CustomEvent) => { + const { relayUrl } = event.detail + if (normalizeUrl(relayUrl) === normalizedUrl) { + // Trigger a refresh of the note list + noteListRef.current?.refresh() + } + } + + window.addEventListener('relay-refresh-needed', handleRelayRefresh as EventListener) + + return () => { + window.removeEventListener('relay-refresh-needed', handleRelayRefresh as EventListener) + } + }, [normalizedUrl]) + if (!normalizedUrl) { return } @@ -52,6 +73,7 @@ export default function Relay({ url, className }: { url?: string; className?: st )} normalizeUrl(url) || url), // Fast, well-connected relays + ...userRelays.map(url => normalizeUrl(url) || url) // User's mailbox relays ])) const filters: (Omit & { diff --git a/src/components/SaveRelayDropdownMenu/index.tsx b/src/components/SaveRelayDropdownMenu/index.tsx index e697212..698def4 100644 --- a/src/components/SaveRelayDropdownMenu/index.tsx +++ b/src/components/SaveRelayDropdownMenu/index.tsx @@ -134,7 +134,10 @@ function RelayItem({ urls }: { urls: string[] }) { if (isSmallScreen) { return ( - + {isLoading ? '...' : (saved ? : )} {isLoading ? t('Loading...') : (saved ? t('Unfavorite') : t('Favorite'))} @@ -168,7 +171,10 @@ function RelaySetItem({ set, urls }: { set: TRelaySet; urls: string[] }) { } else { updateRelaySet({ ...set, - relayUrls: Array.from(new Set([...set.relayUrls, ...urls])) + relayUrls: Array.from(new Set([ + ...set.relayUrls.map(url => normalizeUrl(url) || url), + ...urls.map(url => normalizeUrl(url) || url) + ])) }) } } diff --git a/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx b/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx index 12050fb..329319d 100644 --- a/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx +++ b/src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx @@ -17,7 +17,7 @@ import { TDraftEvent, TRelaySet } from '@/types' import { NostrEvent } from 'nostr-tools' import { prefixNostrAddresses } from '@/lib/nostr-address' import { showPublishingError } from '@/lib/publishing-feedback' -import { simplifyUrl } from '@/lib/url' +import { normalizeUrl, simplifyUrl } from '@/lib/url' import dayjs from 'dayjs' import { extractHashtagsFromContent, normalizeTopic } from '@/lib/discussion-topics' import DiscussionContent from '@/components/Note/DiscussionContent' @@ -105,7 +105,10 @@ export default function CreateThreadDialog({ const relaySet = initialRelay ? relaySets.find(set => set.id === initialRelay) : null if (relaySet) { // Include relays from the selected set along with available relays - return Array.from(new Set([...availableRelays, ...relaySet.relayUrls])) + return Array.from(new Set([ + ...availableRelays.map(url => normalizeUrl(url) || url), + ...relaySet.relayUrls.map(url => normalizeUrl(url) || url) + ])) } return availableRelays }, [availableRelays, relaySets, initialRelay]) diff --git a/src/pages/primary/DiscussionsPage/index.tsx b/src/pages/primary/DiscussionsPage/index.tsx index 96cfd1f..93f4df9 100644 --- a/src/pages/primary/DiscussionsPage/index.tsx +++ b/src/pages/primary/DiscussionsPage/index.tsx @@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { DEFAULT_FAVORITE_RELAYS, FAST_READ_RELAY_URLS } from '@/constants' +import { normalizeUrl } from '@/lib/url' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useNostr } from '@/providers/NostrProvider' import { forwardRef, useEffect, useState, useCallback, useMemo } from 'react' @@ -166,12 +167,12 @@ const DiscussionsPage = forwardRef((_, ref) => { } } - // Deduplicate and combine all relays: favorite relays, user write relays, stored relay sets, and fast read relays + // Normalize and deduplicate all relays: favorite relays, user write relays, stored relay sets, and fast read relays const allRelays = Array.from(new Set([ - ...availableRelays, - ...userWriteRelays, - ...storedRelaySetRelays, - ...FAST_READ_RELAY_URLS + ...availableRelays.map(url => normalizeUrl(url) || url), + ...userWriteRelays.map(url => normalizeUrl(url) || url), + ...storedRelaySetRelays.map(url => normalizeUrl(url) || url), + ...FAST_READ_RELAY_URLS.map(url => normalizeUrl(url) || url) ])) setRelayUrls(allRelays) diff --git a/src/pages/secondary/NoteListPage/index.tsx b/src/pages/secondary/NoteListPage/index.tsx index 2f5b1fe..33fe62f 100644 --- a/src/pages/secondary/NoteListPage/index.tsx +++ b/src/pages/secondary/NoteListPage/index.tsx @@ -2,6 +2,7 @@ import { Favicon } from '@/components/Favicon' import NormalFeed from '@/components/NormalFeed' import { Button } from '@/components/ui/button' import { BIG_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' +import { normalizeUrl } from '@/lib/url' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { toProfileList } from '@/lib/link' import { fetchPubkeysFromDomain, getWellKnownNip05Url } from '@/lib/nip05' @@ -71,7 +72,10 @@ const NoteListPage = forwardRef(({ index }: { index?: number }, ref) => { setSubRequests([ { filter: { '#I': [externalContentId], ...(kinds.length > 0 ? { kinds } : {}) }, - urls: BIG_RELAY_URLS.concat(relayList?.write || []) + urls: Array.from(new Set([ + ...BIG_RELAY_URLS.map(url => normalizeUrl(url) || url), + ...(relayList?.write || []).map(url => normalizeUrl(url) || url) + ])) } ]) return diff --git a/src/pages/secondary/NotePage/NotFound.tsx b/src/pages/secondary/NotePage/NotFound.tsx index e352690..2eec41b 100644 --- a/src/pages/secondary/NotePage/NotFound.tsx +++ b/src/pages/secondary/NotePage/NotFound.tsx @@ -1,5 +1,6 @@ import ClientSelect from '@/components/ClientSelect' import { Button } from '@/components/ui/button' +import { normalizeUrl } from '@/lib/url' import client from '@/services/client.service' import { AlertCircle, Search } from 'lucide-react' import { nip19 } from 'nostr-tools' @@ -26,7 +27,7 @@ export default function NotFound({ // Get all relays that would be tried in tiers 1-3 (already tried) const alreadyTriedRelays = await client.getAlreadyTriedRelays(bech32Id) - const externalRelays: string[] = [] + let externalRelays: string[] = [] // Parse relay hints and author from bech32 ID if (!/^[0-9a-f]{64}$/.test(bech32Id)) { @@ -44,6 +45,9 @@ export default function NotFound({ const authorRelayList = await client.fetchRelayList(data.pubkey) externalRelays.push(...authorRelayList.write.slice(0, 6)) } + // Normalize and deduplicate external relays + externalRelays = externalRelays.map(url => normalizeUrl(url) || url) + externalRelays = Array.from(new Set(externalRelays)) } catch (err) { console.error('Failed to parse external relays:', err) } @@ -55,7 +59,9 @@ export default function NotFound({ // Filter out relays that were already tried in tiers 1-3 const newRelays = externalRelays.filter(relay => !alreadyTriedRelays.includes(relay)) - setExternalRelays(Array.from(new Set(newRelays))) + // Normalize and deduplicate final relay list + const normalizedRelays = newRelays.map(url => normalizeUrl(url) || url) + setExternalRelays(Array.from(new Set(normalizedRelays))) } getExternalRelays() diff --git a/src/providers/FavoriteRelaysProvider.tsx b/src/providers/FavoriteRelaysProvider.tsx index a568ee6..13cc992 100644 --- a/src/providers/FavoriteRelaysProvider.tsx +++ b/src/providers/FavoriteRelaysProvider.tsx @@ -99,8 +99,12 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode ) setRelaySetEvents(storedRelaySetEvents.filter(Boolean) as Event[]) + const normalizedRelays = [ + ...(relayList?.write ?? []).map(url => normalizeUrl(url) || url), + ...BIG_RELAY_URLS.map(url => normalizeUrl(url) || url) + ] const newRelaySetEvents = await client.fetchEvents( - (relayList?.write ?? []).concat(BIG_RELAY_URLS).slice(0, 5), + Array.from(new Set(normalizedRelays)).slice(0, 5), { kinds: [kinds.Relaysets], authors: [pubkey], diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 41bac59..278b2ab 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -13,6 +13,7 @@ import { minePow } from '@/lib/event' import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata' +import { normalizeUrl } from '@/lib/url' import { formatPubkey, pubkeyToNpub } from '@/lib/pubkey' import { showPublishingFeedback, showSimplePublishSuccess } from '@/lib/publishing-feedback' import client from '@/services/client.service' @@ -274,7 +275,11 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { } setRelayList(relayList) - const fetchRelays = relayList.write.concat(BIG_RELAY_URLS).slice(0, 4) + const normalizedRelays = [ + ...relayList.write.map(url => normalizeUrl(url) || url), + ...BIG_RELAY_URLS.map(url => normalizeUrl(url) || url) + ] + const fetchRelays = Array.from(new Set(normalizedRelays)).slice(0, 4) const events = await client.fetchEvents(fetchRelays, [ { kinds: [ diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 1b95451..e8e0354 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -144,9 +144,12 @@ class ClientService extends EventTarget { const relayList = this.pubkey ? await this.fetchRelayList(this.pubkey) : { write: [], read: [] } const senderWriteRelays = relayList?.write.slice(0, 6) ?? [] const recipientReadRelays = Array.from(new Set(_additionalRelayUrls)) - relays = senderWriteRelays.concat(recipientReadRelays) + // Normalize and deduplicate the combined relay list + const normalizedSenderRelays = senderWriteRelays.map(url => normalizeUrl(url) || url) + const normalizedRecipientRelays = recipientReadRelays.map(url => normalizeUrl(url) || url) + relays = Array.from(new Set(normalizedSenderRelays.concat(normalizedRecipientRelays))) } - + if (!relays.length) { relays.push(...FAST_WRITE_RELAY_URLS) } @@ -1085,20 +1088,20 @@ class ClientService extends EventTarget { // Tier 1: User's read relays + fast read relays const tier1Relays = Array.from(new Set([ - ...userRelayList.read, - ...FAST_READ_RELAY_URLS + ...userRelayList.read.map(url => normalizeUrl(url) || url), + ...FAST_READ_RELAY_URLS.map(url => normalizeUrl(url) || url) ])) // Tier 2: User's write relays + fast write relays const tier2Relays = Array.from(new Set([ - ...userRelayList.write, - ...FAST_WRITE_RELAY_URLS + ...userRelayList.write.map(url => normalizeUrl(url) || url), + ...FAST_WRITE_RELAY_URLS.map(url => normalizeUrl(url) || url) ])) // Tier 3: Search relays + big relays const tier3Relays = Array.from(new Set([ - ...SEARCHABLE_RELAY_URLS, - ...BIG_RELAY_URLS + ...SEARCHABLE_RELAY_URLS.map(url => normalizeUrl(url) || url), + ...BIG_RELAY_URLS.map(url => normalizeUrl(url) || url) ])) return Array.from(new Set([ @@ -1148,7 +1151,9 @@ class ClientService extends EventTarget { const seenOn = this.getSeenEventRelayUrls(id) externalRelays.push(...seenOn) - const uniqueExternalRelays = Array.from(new Set(externalRelays)) + // Normalize and deduplicate the combined external relays + const normalizedExternalRelays = externalRelays.map(url => normalizeUrl(url) || url) + const uniqueExternalRelays = Array.from(new Set(normalizedExternalRelays)) if (uniqueExternalRelays.length === 0) { return undefined @@ -1209,24 +1214,24 @@ class ClientService extends EventTarget { // Tier 1: User's read relays + fast read relays (deduplicated) const tier1Relays = Array.from(new Set([ - ...userRelayList.read, - ...FAST_READ_RELAY_URLS + ...userRelayList.read.map(url => normalizeUrl(url) || url), + ...FAST_READ_RELAY_URLS.map(url => normalizeUrl(url) || url) ])) const tier1Event = await this.tryHarderToFetchEvent(tier1Relays, filter) if (tier1Event) { return tier1Event } // Tier 2: User's write relays + fast write relays (deduplicated) const tier2Relays = Array.from(new Set([ - ...userRelayList.write, - ...FAST_WRITE_RELAY_URLS + ...userRelayList.write.map(url => normalizeUrl(url) || url), + ...FAST_WRITE_RELAY_URLS.map(url => normalizeUrl(url) || url) ])) const tier2Event = await this.tryHarderToFetchEvent(tier2Relays, filter) if (tier2Event) { return tier2Event } // Tier 3: Search relays + big relays (deduplicated) const tier3Relays = Array.from(new Set([ - ...SEARCHABLE_RELAY_URLS, - ...BIG_RELAY_URLS + ...SEARCHABLE_RELAY_URLS.map(url => normalizeUrl(url) || url), + ...BIG_RELAY_URLS.map(url => normalizeUrl(url) || url) ])) const tier3Event = await this.tryHarderToFetchEvent(tier3Relays, filter) if (tier3Event) { return tier3Event }