diff --git a/src/components/Note/Poll.tsx b/src/components/Note/Poll.tsx index ad834f6..26ceb36 100644 --- a/src/components/Note/Poll.tsx +++ b/src/components/Note/Poll.tsx @@ -1,5 +1,5 @@ import { Button } from '@/components/ui/button' -import { POLL_TYPE } from '@/constants' +import { BIG_RELAY_URLS, POLL_TYPE } from '@/constants' import { useTranslatedEvent } from '@/hooks' import { useFetchPollResults } from '@/hooks/useFetchPollResults' import { createPollResponseDraftEvent } from '@/lib/draft-event' @@ -253,9 +253,9 @@ export default function Poll({ event, className }: { event: Event; className?: s async function ensurePollRelays(creator: string, poll: { relayUrls: string[] }) { const relays = poll.relayUrls.slice(0, 4) + // Privacy: Use defaults instead of fetching creator's relays if (!relays.length) { - const relayList = await client.fetchRelayList(creator) - relays.push(...relayList.read.slice(0, 4)) + relays.push(...BIG_RELAY_URLS.slice(0, 4)) } return relays } diff --git a/src/components/NoteStats/LikeButton.tsx b/src/components/NoteStats/LikeButton.tsx index 9a91a79..3048583 100644 --- a/src/components/NoteStats/LikeButton.tsx +++ b/src/components/NoteStats/LikeButton.tsx @@ -80,8 +80,7 @@ export default function LikeButton({ event }: { event: Event }) { } const reaction = createReactionDraftEvent(event, emoji) - const seenOn = client.getSeenEventRelayUrls(event.id) - const evt = await publish(reaction, { additionalRelayUrls: seenOn }) + const evt = await publish(reaction) noteStatsService.updateNoteStatsByEvents([evt]) } catch (error) { console.error('like failed', error) diff --git a/src/components/NoteStats/Likes.tsx b/src/components/NoteStats/Likes.tsx index 87143f5..0eb1dd7 100644 --- a/src/components/NoteStats/Likes.tsx +++ b/src/components/NoteStats/Likes.tsx @@ -45,8 +45,7 @@ export default function Likes({ event }: { event: Event }) { try { const reaction = createReactionDraftEvent(event, emoji) - const seenOn = client.getSeenEventRelayUrls(event.id) - const evt = await publish(reaction, { additionalRelayUrls: seenOn }) + const evt = await publish(reaction) noteStatsService.updateNoteStatsByEvents([evt]) } catch (error) { console.error('like failed', error) diff --git a/src/components/NoteStats/VoteButtons.tsx b/src/components/NoteStats/VoteButtons.tsx index fa7eb7f..49799f4 100644 --- a/src/components/NoteStats/VoteButtons.tsx +++ b/src/components/NoteStats/VoteButtons.tsx @@ -61,22 +61,19 @@ export default function VoteButtons({ event }: { event: Event }) { if (existingVote) { // Remove vote by creating a reaction with the same emoji (this will toggle it off) const reaction = createReactionDraftEvent(event, emoji) - const seenOn = client.getSeenEventRelayUrls(event.id) - const evt = await publish(reaction, { additionalRelayUrls: seenOn }) + const evt = await publish(reaction) noteStatsService.updateNoteStatsByEvents([evt]) } else { // If user voted the opposite way, first remove the old vote if (userVote) { const oldEmoji = userVote === 'up' ? '⬆️' : '⬇️' const removeReaction = createReactionDraftEvent(event, oldEmoji) - const seenOn = client.getSeenEventRelayUrls(event.id) - await publish(removeReaction, { additionalRelayUrls: seenOn }) + await publish(removeReaction) } // Then add the new vote const reaction = createReactionDraftEvent(event, emoji) - const seenOn = client.getSeenEventRelayUrls(event.id) - const evt = await publish(reaction, { additionalRelayUrls: seenOn }) + const evt = await publish(reaction) noteStatsService.updateNoteStatsByEvents([evt]) } } catch (error) { diff --git a/src/components/PostEditor/PostRelaySelector.tsx b/src/components/PostEditor/PostRelaySelector.tsx index 481f49a..0dcb326 100644 --- a/src/components/PostEditor/PostRelaySelector.tsx +++ b/src/components/PostEditor/PostRelaySelector.tsx @@ -51,15 +51,10 @@ export default function PostRelaySelector({ const { relayUrls } = useCurrentRelays() const { relaySets, favoriteRelays } = useFavoriteRelays() const [postTargetItems, setPostTargetItems] = useState([]) - const parentEventSeenOnRelays = useMemo(() => { - if (!parentEvent || !isProtectedEvent(parentEvent)) { - return [] - } - return client.getSeenEventRelayUrls(parentEvent.id) - }, [parentEvent]) + // Privacy: Only show user's own relays + defaults, never other users' relays const selectableRelays = useMemo(() => { - return Array.from(new Set(parentEventSeenOnRelays.concat(relayUrls).concat(favoriteRelays))) - }, [parentEventSeenOnRelays, relayUrls, favoriteRelays]) + return Array.from(new Set(relayUrls.concat(favoriteRelays))) + }, [relayUrls, favoriteRelays]) const description = useMemo(() => { if (postTargetItems.length === 0) { return t('No relays selected') diff --git a/src/components/Profile/ProfileFeed.tsx b/src/components/Profile/ProfileFeed.tsx index dd5e5e2..13c9c7e 100644 --- a/src/components/Profile/ProfileFeed.tsx +++ b/src/components/Profile/ProfileFeed.tsx @@ -40,27 +40,26 @@ export default function ProfileFeed({ useEffect(() => { const init = async () => { + // Privacy: Only use user's own relays + defaults, never connect to other users' relays + const myRelayList = myPubkey ? await client.fetchRelayList(myPubkey) : { write: [], read: [] } + const userRelays = myRelayList.read.concat(BIG_RELAY_URLS) + if (listMode === 'you') { if (!myPubkey) { setSubRequests([]) return } - const [relayList, myRelayList] = await Promise.all([ - client.fetchRelayList(pubkey), - client.fetchRelayList(myPubkey) - ]) - setSubRequests([ { - urls: myRelayList.write.concat(BIG_RELAY_URLS).slice(0, 5), + urls: userRelays.slice(0, 5), filter: { authors: [myPubkey], '#p': [pubkey] } }, { - urls: relayList.write.concat(BIG_RELAY_URLS).slice(0, 5), + urls: userRelays.slice(0, 5), filter: { authors: [pubkey], '#p': [myPubkey] @@ -70,10 +69,9 @@ export default function ProfileFeed({ return } - const relayList = await client.fetchRelayList(pubkey) setSubRequests([ { - urls: relayList.write.concat(BIG_RELAY_URLS).slice(0, 8), + urls: userRelays.slice(0, 8), filter: { authors: [pubkey] } @@ -81,7 +79,7 @@ export default function ProfileFeed({ ]) } init() - }, [pubkey, listMode]) + }, [pubkey, listMode, myPubkey]) const handleListModeChange = (mode: TNoteListMode) => { setListMode(mode) diff --git a/src/components/QuoteList/index.tsx b/src/components/QuoteList/index.tsx index bf4c9cf..acf62e9 100644 --- a/src/components/QuoteList/index.tsx +++ b/src/components/QuoteList/index.tsx @@ -29,14 +29,9 @@ export default function QuoteList({ event, className }: { event: Event; classNam setEvents([]) setHasMore(true) - const relayList = await client.fetchRelayList(event.pubkey) - // Include user's mailbox relays for better quote discovery + // Privacy: Only use user's own relays + defaults, never connect to other users' relays const userRelays = userRelayList?.read || [] - const relayUrls = Array.from(new Set(relayList.read.concat(userRelays).concat(FAST_READ_RELAY_URLS))) - const seenOn = client.getSeenEventRelayUrls(event.id) - relayUrls.unshift(...seenOn) - // Deduplicate the final list including seenOn relays - const finalRelayUrls = Array.from(new Set(relayUrls)) + const finalRelayUrls = Array.from(new Set(userRelays.concat(FAST_READ_RELAY_URLS))) const { closer, timelineKey } = await client.subscribeTimeline( [ diff --git a/src/components/ReplyNoteList/index.tsx b/src/components/ReplyNoteList/index.tsx index 06d8c50..e587d83 100644 --- a/src/components/ReplyNoteList/index.tsx +++ b/src/components/ReplyNoteList/index.tsx @@ -219,20 +219,11 @@ export default function ReplyNoteList({ index, event, sort = 'oldest' }: { index setLoading(true) try { - // Include user's mailbox relays for better reply discovery + // Privacy: Only use user's own relays + defaults, never connect to other users' relays const userRelays = userRelayList?.read || [] - const seenOn = - rootInfo.type === 'E' - ? client.getSeenEventRelayUrls(rootInfo.id) - : rootInfo.type === 'A' - ? client.getSeenEventRelayUrls(rootInfo.eventId) - : [] - - // Optimize relay selection: prioritize seen relays, then fast relays, then user relays const finalRelayUrls = Array.from(new Set([ - ...seenOn, // Highest priority: relays where the event was seen - ...FAST_READ_RELAY_URLS, // Second priority: fast, well-connected relays - ...userRelays // Third priority: user's mailbox relays + ...FAST_READ_RELAY_URLS, // Fast, well-connected relays + ...userRelays // User's mailbox relays ])) const filters: (Omit & { diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 5c406df..854b5ea 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -655,11 +655,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { const deletionRequest = await signEvent(createDeletionRequestDraftEvent(targetEvent)) - const seenOn = client.getSeenEventRelayUrls(targetEvent.id) - const relays = await client.determineTargetRelays(targetEvent, { - specifiedRelayUrls: isProtectedEvent(targetEvent) ? seenOn : undefined, - additionalRelayUrls: seenOn - }) + // Privacy: Only use user's own relays, never connect to "seen on" relays + const relays = await client.determineTargetRelays(targetEvent) await client.publishEvent(relays, deletionRequest) diff --git a/src/services/client.service.ts b/src/services/client.service.ts index e22eced..77b7623 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -7,7 +7,7 @@ import { } from '@/lib/event' import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata' import { formatPubkey, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey' -import { getPubkeysFromPTags, getServersFromServerTags, tagNameEquals } from '@/lib/tag' +import { getPubkeysFromPTags, getServersFromServerTags } from '@/lib/tag' import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url' import { isSafari } from '@/lib/utils' import { ISigner, TProfile, TPublishOptions, TRelayList, TSubRequestFilter } from '@/types' @@ -93,19 +93,14 @@ class ClientService extends EventTarget { event: NEvent, { specifiedRelayUrls, additionalRelayUrls }: TPublishOptions = {} ) { - if (event.kind === kinds.Report) { - const targetEventId = event.tags.find(tagNameEquals('e'))?.[1] - if (targetEventId) { - return this.getSeenEventRelayUrls(targetEventId) - } - } - let relays: string[] if (specifiedRelayUrls?.length) { relays = specifiedRelayUrls } else { const _additionalRelayUrls: string[] = additionalRelayUrls ?? [] - if (!specifiedRelayUrls?.length && ![kinds.Contacts, kinds.Mutelist].includes(event.kind)) { + + // For kind 1 (notes) and kind 24 (public messages), publish to mentioned users' inboxes + if (event.kind === kinds.ShortTextNote || event.kind === ExtendedKind.PUBLIC_MESSAGE) { const mentions: string[] = [] event.tags.forEach(([tagName, tagValue]) => { if ( @@ -124,6 +119,7 @@ class ClientService extends EventTarget { }) } } + if ( [ kinds.RelayList, @@ -136,7 +132,8 @@ class ClientService extends EventTarget { _additionalRelayUrls.push(...BIG_RELAY_URLS) } - const relayList = await this.fetchRelayList(event.pubkey) + // Use current user's relay list + 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) @@ -1030,14 +1027,8 @@ class ClientService extends EventTarget { return event } - // Then try the relays where this event was originally seen - const seenOnRelays = this.getSeenEventRelayUrls(id) - if (seenOnRelays.length > 0) { - const seenOnEvent = await this.tryHarderToFetchEvent(seenOnRelays, { ids: [id], limit: 1 }, true) - if (seenOnEvent) { - return seenOnEvent - } - } + // Privacy: Don't try "seen on" relays - only use defaults + // Fallback to BIG_RELAY_URLS if not found // Third, try the provided relay URLs if (relayUrls.length > 0) { @@ -1047,11 +1038,10 @@ class ClientService extends EventTarget { } } - // Finally, try all available relays as a last resort + // Privacy: Use defaults and provided relays only const allAvailableRelays = Array.from(new Set([ ...FAST_READ_RELAY_URLS, ...FAST_WRITE_RELAY_URLS, - ...seenOnRelays, ...relayUrls ])) @@ -1061,7 +1051,6 @@ class ClientService extends EventTarget { private async _fetchEvent(id: string): Promise { let filter: Filter | undefined let relays: string[] = [] - let author: string | undefined if (/^[0-9a-f]{64}$/.test(id)) { filter = { ids: [id] } } else { @@ -1073,7 +1062,6 @@ class ClientService extends EventTarget { case 'nevent': filter = { ids: [data.id] } if (data.relays) relays = data.relays - if (data.author) author = data.author break case 'naddr': filter = { @@ -1081,7 +1069,6 @@ class ClientService extends EventTarget { kinds: [data.kind], limit: 1 } - author = data.pubkey if (data.identifier) { filter['#d'] = [data.identifier] } @@ -1096,9 +1083,9 @@ class ClientService extends EventTarget { if (filter.ids) { event = await this.fetchEventById(relays, filter.ids[0]) } else { - if (author) { - const relayList = await this.fetchRelayList(author) - relays.push(...relayList.write.slice(0, 4)) + // Privacy: Don't fetch author's relays, use defaults + if (!relays.length) { + relays.push(...BIG_RELAY_URLS) } event = await this.tryHarderToFetchEvent(relays, filter) } @@ -1115,12 +1102,8 @@ class ClientService extends EventTarget { filter: Filter, alreadyFetchedFromBigRelays = false ) { - if (!relayUrls.length && filter.authors?.length) { - const relayList = await this.fetchRelayList(filter.authors[0]) - relayUrls = alreadyFetchedFromBigRelays - ? relayList.write.filter((url) => !BIG_RELAY_URLS.includes(url)).slice(0, 4) - : relayList.write.slice(0, 4) - } else if (!relayUrls.length && !alreadyFetchedFromBigRelays) { + // Privacy: Don't fetch author's relays, only use provided relays or defaults + if (!relayUrls.length && !alreadyFetchedFromBigRelays) { relayUrls = BIG_RELAY_URLS } if (!relayUrls.length) return @@ -1691,50 +1674,21 @@ class ClientService extends EventTarget { } async generateSubRequestsForPubkeys(pubkeys: string[], myPubkey?: string | null) { + // Privacy: Only use user's own relays + defaults, never fetch other users' relays + let urls = BIG_RELAY_URLS + if (myPubkey) { + const relayList = await this.fetchRelayList(myPubkey) + urls = relayList.read.concat(BIG_RELAY_URLS).slice(0, 5) + } + // If many websocket connections are initiated simultaneously, it will be // very slow on Safari (for unknown reason) if (isSafari()) { - let urls = BIG_RELAY_URLS - if (myPubkey) { - const relayList = await this.fetchRelayList(myPubkey) - urls = relayList.read.concat(BIG_RELAY_URLS).slice(0, 5) - } return [{ urls, filter: { authors: pubkeys } }] } - const relayLists = await this.fetchRelayLists(pubkeys) - const group: Record> = {} - relayLists.forEach((relayList, index) => { - relayList.write.slice(0, 4).forEach((url) => { - if (!group[url]) { - group[url] = new Set() - } - group[url].add(pubkeys[index]) - }) - }) - - const relayCount = Object.keys(group).length - const coveredCount = new Map() - Object.entries(group) - .sort(([, a], [, b]) => b.size - a.size) - .forEach(([url, pubkeys]) => { - if ( - relayCount > 10 && - pubkeys.size < 10 && - Array.from(pubkeys).every((pubkey) => (coveredCount.get(pubkey) ?? 0) >= 2) - ) { - delete group[url] - } else { - pubkeys.forEach((pubkey) => { - coveredCount.set(pubkey, (coveredCount.get(pubkey) ?? 0) + 1) - }) - } - }) - - return Object.entries(group).map(([url, authors]) => ({ - urls: [url], - filter: { authors: Array.from(authors) } - })) + // Simplified: Use user's relays for all followed users instead of individual relay lists + return [{ urls, filter: { authors: pubkeys } }] } } diff --git a/src/services/lightning.service.ts b/src/services/lightning.service.ts index 924b151..63ae72c 100644 --- a/src/services/lightning.service.ts +++ b/src/services/lightning.service.ts @@ -47,9 +47,9 @@ class LightningService { ? { recipient: recipientOrEvent } : { recipient: recipientOrEvent.pubkey, event: recipientOrEvent } - const [profile, receiptRelayList, senderRelayList] = await Promise.all([ + // Privacy: Only use current user's relays + defaults + const [profile, senderRelayList] = await Promise.all([ client.fetchProfile(recipient, true), - client.fetchRelayList(recipient), sender ? client.fetchRelayList(sender) : Promise.resolve({ read: BIG_RELAY_URLS, write: BIG_RELAY_URLS }) @@ -66,10 +66,8 @@ class LightningService { const zapRequestDraft = makeZapRequest({ ...(event ? { event } : { pubkey: recipient }), amount, - relays: receiptRelayList.read - .slice(0, 4) - .concat(senderRelayList.write.slice(0, 3)) - .concat(BIG_RELAY_URLS), + // Privacy: Only use sender's relays + defaults, not recipient's relays + relays: senderRelayList.write.slice(0, 4).concat(BIG_RELAY_URLS), comment }) const zapRequest = await client.signer.signEvent(zapRequestDraft) @@ -175,8 +173,8 @@ class LightningService { if (this.recentSupportersCache) { return this.recentSupportersCache } - const relayList = await client.fetchRelayList(CODY_PUBKEY) - const events = await client.fetchEvents(relayList.read.slice(0, 4), { + // Privacy: Use defaults instead of fetching CODY_PUBKEY's relays + const events = await client.fetchEvents(BIG_RELAY_URLS.slice(0, 4), { authors: ['79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432'], // alby kinds: [kinds.Zap], '#p': OFFICIAL_PUBKEYS, diff --git a/src/services/note-stats.service.ts b/src/services/note-stats.service.ts index 9403d52..1d74803 100644 --- a/src/services/note-stats.service.ts +++ b/src/services/note-stats.service.ts @@ -35,8 +35,9 @@ class NoteStatsService { if (oldStats?.updatedAt) { since = oldStats.updatedAt } + // Privacy: Only use current user's relays + defaults, never connect to other users' relays const [relayList, authorProfile] = await Promise.all([ - client.fetchRelayList(event.pubkey), + pubkey ? client.fetchRelayList(pubkey) : Promise.resolve({ write: [], read: [] }), client.fetchProfile(event.pubkey) ])