Browse Source

removed most external relay connections, except for publishing

imwald
Silberengel 5 months ago
parent
commit
2b55e7a87a
  1. 6
      src/components/Note/Poll.tsx
  2. 3
      src/components/NoteStats/LikeButton.tsx
  3. 3
      src/components/NoteStats/Likes.tsx
  4. 9
      src/components/NoteStats/VoteButtons.tsx
  5. 11
      src/components/PostEditor/PostRelaySelector.tsx
  6. 18
      src/components/Profile/ProfileFeed.tsx
  7. 9
      src/components/QuoteList/index.tsx
  8. 15
      src/components/ReplyNoteList/index.tsx
  9. 7
      src/providers/NostrProvider/index.tsx
  10. 94
      src/services/client.service.ts
  11. 14
      src/services/lightning.service.ts
  12. 3
      src/services/note-stats.service.ts

6
src/components/Note/Poll.tsx

@ -1,5 +1,5 @@
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { POLL_TYPE } from '@/constants' import { BIG_RELAY_URLS, POLL_TYPE } from '@/constants'
import { useTranslatedEvent } from '@/hooks' import { useTranslatedEvent } from '@/hooks'
import { useFetchPollResults } from '@/hooks/useFetchPollResults' import { useFetchPollResults } from '@/hooks/useFetchPollResults'
import { createPollResponseDraftEvent } from '@/lib/draft-event' 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[] }) { async function ensurePollRelays(creator: string, poll: { relayUrls: string[] }) {
const relays = poll.relayUrls.slice(0, 4) const relays = poll.relayUrls.slice(0, 4)
// Privacy: Use defaults instead of fetching creator's relays
if (!relays.length) { if (!relays.length) {
const relayList = await client.fetchRelayList(creator) relays.push(...BIG_RELAY_URLS.slice(0, 4))
relays.push(...relayList.read.slice(0, 4))
} }
return relays return relays
} }

3
src/components/NoteStats/LikeButton.tsx

@ -80,8 +80,7 @@ export default function LikeButton({ event }: { event: Event }) {
} }
const reaction = createReactionDraftEvent(event, emoji) const reaction = createReactionDraftEvent(event, emoji)
const seenOn = client.getSeenEventRelayUrls(event.id) const evt = await publish(reaction)
const evt = await publish(reaction, { additionalRelayUrls: seenOn })
noteStatsService.updateNoteStatsByEvents([evt]) noteStatsService.updateNoteStatsByEvents([evt])
} catch (error) { } catch (error) {
console.error('like failed', error) console.error('like failed', error)

3
src/components/NoteStats/Likes.tsx

@ -45,8 +45,7 @@ export default function Likes({ event }: { event: Event }) {
try { try {
const reaction = createReactionDraftEvent(event, emoji) const reaction = createReactionDraftEvent(event, emoji)
const seenOn = client.getSeenEventRelayUrls(event.id) const evt = await publish(reaction)
const evt = await publish(reaction, { additionalRelayUrls: seenOn })
noteStatsService.updateNoteStatsByEvents([evt]) noteStatsService.updateNoteStatsByEvents([evt])
} catch (error) { } catch (error) {
console.error('like failed', error) console.error('like failed', error)

9
src/components/NoteStats/VoteButtons.tsx

@ -61,22 +61,19 @@ export default function VoteButtons({ event }: { event: Event }) {
if (existingVote) { if (existingVote) {
// Remove vote by creating a reaction with the same emoji (this will toggle it off) // Remove vote by creating a reaction with the same emoji (this will toggle it off)
const reaction = createReactionDraftEvent(event, emoji) const reaction = createReactionDraftEvent(event, emoji)
const seenOn = client.getSeenEventRelayUrls(event.id) const evt = await publish(reaction)
const evt = await publish(reaction, { additionalRelayUrls: seenOn })
noteStatsService.updateNoteStatsByEvents([evt]) noteStatsService.updateNoteStatsByEvents([evt])
} else { } else {
// If user voted the opposite way, first remove the old vote // If user voted the opposite way, first remove the old vote
if (userVote) { if (userVote) {
const oldEmoji = userVote === 'up' ? '⬆' : '⬇' const oldEmoji = userVote === 'up' ? '⬆' : '⬇'
const removeReaction = createReactionDraftEvent(event, oldEmoji) const removeReaction = createReactionDraftEvent(event, oldEmoji)
const seenOn = client.getSeenEventRelayUrls(event.id) await publish(removeReaction)
await publish(removeReaction, { additionalRelayUrls: seenOn })
} }
// Then add the new vote // Then add the new vote
const reaction = createReactionDraftEvent(event, emoji) const reaction = createReactionDraftEvent(event, emoji)
const seenOn = client.getSeenEventRelayUrls(event.id) const evt = await publish(reaction)
const evt = await publish(reaction, { additionalRelayUrls: seenOn })
noteStatsService.updateNoteStatsByEvents([evt]) noteStatsService.updateNoteStatsByEvents([evt])
} }
} catch (error) { } catch (error) {

11
src/components/PostEditor/PostRelaySelector.tsx

@ -51,15 +51,10 @@ export default function PostRelaySelector({
const { relayUrls } = useCurrentRelays() const { relayUrls } = useCurrentRelays()
const { relaySets, favoriteRelays } = useFavoriteRelays() const { relaySets, favoriteRelays } = useFavoriteRelays()
const [postTargetItems, setPostTargetItems] = useState<TPostTargetItem[]>([]) const [postTargetItems, setPostTargetItems] = useState<TPostTargetItem[]>([])
const parentEventSeenOnRelays = useMemo(() => { // Privacy: Only show user's own relays + defaults, never other users' relays
if (!parentEvent || !isProtectedEvent(parentEvent)) {
return []
}
return client.getSeenEventRelayUrls(parentEvent.id)
}, [parentEvent])
const selectableRelays = useMemo(() => { const selectableRelays = useMemo(() => {
return Array.from(new Set(parentEventSeenOnRelays.concat(relayUrls).concat(favoriteRelays))) return Array.from(new Set(relayUrls.concat(favoriteRelays)))
}, [parentEventSeenOnRelays, relayUrls, favoriteRelays]) }, [relayUrls, favoriteRelays])
const description = useMemo(() => { const description = useMemo(() => {
if (postTargetItems.length === 0) { if (postTargetItems.length === 0) {
return t('No relays selected') return t('No relays selected')

18
src/components/Profile/ProfileFeed.tsx

@ -40,27 +40,26 @@ export default function ProfileFeed({
useEffect(() => { useEffect(() => {
const init = async () => { 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 (listMode === 'you') {
if (!myPubkey) { if (!myPubkey) {
setSubRequests([]) setSubRequests([])
return return
} }
const [relayList, myRelayList] = await Promise.all([
client.fetchRelayList(pubkey),
client.fetchRelayList(myPubkey)
])
setSubRequests([ setSubRequests([
{ {
urls: myRelayList.write.concat(BIG_RELAY_URLS).slice(0, 5), urls: userRelays.slice(0, 5),
filter: { filter: {
authors: [myPubkey], authors: [myPubkey],
'#p': [pubkey] '#p': [pubkey]
} }
}, },
{ {
urls: relayList.write.concat(BIG_RELAY_URLS).slice(0, 5), urls: userRelays.slice(0, 5),
filter: { filter: {
authors: [pubkey], authors: [pubkey],
'#p': [myPubkey] '#p': [myPubkey]
@ -70,10 +69,9 @@ export default function ProfileFeed({
return return
} }
const relayList = await client.fetchRelayList(pubkey)
setSubRequests([ setSubRequests([
{ {
urls: relayList.write.concat(BIG_RELAY_URLS).slice(0, 8), urls: userRelays.slice(0, 8),
filter: { filter: {
authors: [pubkey] authors: [pubkey]
} }
@ -81,7 +79,7 @@ export default function ProfileFeed({
]) ])
} }
init() init()
}, [pubkey, listMode]) }, [pubkey, listMode, myPubkey])
const handleListModeChange = (mode: TNoteListMode) => { const handleListModeChange = (mode: TNoteListMode) => {
setListMode(mode) setListMode(mode)

9
src/components/QuoteList/index.tsx

@ -29,14 +29,9 @@ export default function QuoteList({ event, className }: { event: Event; classNam
setEvents([]) setEvents([])
setHasMore(true) setHasMore(true)
const relayList = await client.fetchRelayList(event.pubkey) // Privacy: Only use user's own relays + defaults, never connect to other users' relays
// Include user's mailbox relays for better quote discovery
const userRelays = userRelayList?.read || [] const userRelays = userRelayList?.read || []
const relayUrls = Array.from(new Set(relayList.read.concat(userRelays).concat(FAST_READ_RELAY_URLS))) const finalRelayUrls = Array.from(new Set(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 { closer, timelineKey } = await client.subscribeTimeline( const { closer, timelineKey } = await client.subscribeTimeline(
[ [

15
src/components/ReplyNoteList/index.tsx

@ -219,20 +219,11 @@ export default function ReplyNoteList({ index, event, sort = 'oldest' }: { index
setLoading(true) setLoading(true)
try { 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 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([ const finalRelayUrls = Array.from(new Set([
...seenOn, // Highest priority: relays where the event was seen ...FAST_READ_RELAY_URLS, // Fast, well-connected relays
...FAST_READ_RELAY_URLS, // Second priority: fast, well-connected relays ...userRelays // User's mailbox relays
...userRelays // Third priority: user's mailbox relays
])) ]))
const filters: (Omit<Filter, 'since' | 'until'> & { const filters: (Omit<Filter, 'since' | 'until'> & {

7
src/providers/NostrProvider/index.tsx

@ -655,11 +655,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
const deletionRequest = await signEvent(createDeletionRequestDraftEvent(targetEvent)) const deletionRequest = await signEvent(createDeletionRequestDraftEvent(targetEvent))
const seenOn = client.getSeenEventRelayUrls(targetEvent.id) // Privacy: Only use user's own relays, never connect to "seen on" relays
const relays = await client.determineTargetRelays(targetEvent, { const relays = await client.determineTargetRelays(targetEvent)
specifiedRelayUrls: isProtectedEvent(targetEvent) ? seenOn : undefined,
additionalRelayUrls: seenOn
})
await client.publishEvent(relays, deletionRequest) await client.publishEvent(relays, deletionRequest)

94
src/services/client.service.ts

@ -7,7 +7,7 @@ import {
} from '@/lib/event' } from '@/lib/event'
import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata' import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
import { formatPubkey, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey' 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 { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url'
import { isSafari } from '@/lib/utils' import { isSafari } from '@/lib/utils'
import { ISigner, TProfile, TPublishOptions, TRelayList, TSubRequestFilter } from '@/types' import { ISigner, TProfile, TPublishOptions, TRelayList, TSubRequestFilter } from '@/types'
@ -93,19 +93,14 @@ class ClientService extends EventTarget {
event: NEvent, event: NEvent,
{ specifiedRelayUrls, additionalRelayUrls }: TPublishOptions = {} { 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[] let relays: string[]
if (specifiedRelayUrls?.length) { if (specifiedRelayUrls?.length) {
relays = specifiedRelayUrls relays = specifiedRelayUrls
} else { } else {
const _additionalRelayUrls: string[] = additionalRelayUrls ?? [] 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[] = [] const mentions: string[] = []
event.tags.forEach(([tagName, tagValue]) => { event.tags.forEach(([tagName, tagValue]) => {
if ( if (
@ -124,6 +119,7 @@ class ClientService extends EventTarget {
}) })
} }
} }
if ( if (
[ [
kinds.RelayList, kinds.RelayList,
@ -136,7 +132,8 @@ class ClientService extends EventTarget {
_additionalRelayUrls.push(...BIG_RELAY_URLS) _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 senderWriteRelays = relayList?.write.slice(0, 6) ?? []
const recipientReadRelays = Array.from(new Set(_additionalRelayUrls)) const recipientReadRelays = Array.from(new Set(_additionalRelayUrls))
relays = senderWriteRelays.concat(recipientReadRelays) relays = senderWriteRelays.concat(recipientReadRelays)
@ -1030,14 +1027,8 @@ class ClientService extends EventTarget {
return event return event
} }
// Then try the relays where this event was originally seen // Privacy: Don't try "seen on" relays - only use defaults
const seenOnRelays = this.getSeenEventRelayUrls(id) // Fallback to BIG_RELAY_URLS if not found
if (seenOnRelays.length > 0) {
const seenOnEvent = await this.tryHarderToFetchEvent(seenOnRelays, { ids: [id], limit: 1 }, true)
if (seenOnEvent) {
return seenOnEvent
}
}
// Third, try the provided relay URLs // Third, try the provided relay URLs
if (relayUrls.length > 0) { 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([ const allAvailableRelays = Array.from(new Set([
...FAST_READ_RELAY_URLS, ...FAST_READ_RELAY_URLS,
...FAST_WRITE_RELAY_URLS, ...FAST_WRITE_RELAY_URLS,
...seenOnRelays,
...relayUrls ...relayUrls
])) ]))
@ -1061,7 +1051,6 @@ class ClientService extends EventTarget {
private async _fetchEvent(id: string): Promise<NEvent | undefined> { private async _fetchEvent(id: string): Promise<NEvent | undefined> {
let filter: Filter | undefined let filter: Filter | undefined
let relays: string[] = [] let relays: string[] = []
let author: string | undefined
if (/^[0-9a-f]{64}$/.test(id)) { if (/^[0-9a-f]{64}$/.test(id)) {
filter = { ids: [id] } filter = { ids: [id] }
} else { } else {
@ -1073,7 +1062,6 @@ class ClientService extends EventTarget {
case 'nevent': case 'nevent':
filter = { ids: [data.id] } filter = { ids: [data.id] }
if (data.relays) relays = data.relays if (data.relays) relays = data.relays
if (data.author) author = data.author
break break
case 'naddr': case 'naddr':
filter = { filter = {
@ -1081,7 +1069,6 @@ class ClientService extends EventTarget {
kinds: [data.kind], kinds: [data.kind],
limit: 1 limit: 1
} }
author = data.pubkey
if (data.identifier) { if (data.identifier) {
filter['#d'] = [data.identifier] filter['#d'] = [data.identifier]
} }
@ -1096,9 +1083,9 @@ class ClientService extends EventTarget {
if (filter.ids) { if (filter.ids) {
event = await this.fetchEventById(relays, filter.ids[0]) event = await this.fetchEventById(relays, filter.ids[0])
} else { } else {
if (author) { // Privacy: Don't fetch author's relays, use defaults
const relayList = await this.fetchRelayList(author) if (!relays.length) {
relays.push(...relayList.write.slice(0, 4)) relays.push(...BIG_RELAY_URLS)
} }
event = await this.tryHarderToFetchEvent(relays, filter) event = await this.tryHarderToFetchEvent(relays, filter)
} }
@ -1115,12 +1102,8 @@ class ClientService extends EventTarget {
filter: Filter, filter: Filter,
alreadyFetchedFromBigRelays = false alreadyFetchedFromBigRelays = false
) { ) {
if (!relayUrls.length && filter.authors?.length) { // Privacy: Don't fetch author's relays, only use provided relays or defaults
const relayList = await this.fetchRelayList(filter.authors[0]) if (!relayUrls.length && !alreadyFetchedFromBigRelays) {
relayUrls = alreadyFetchedFromBigRelays
? relayList.write.filter((url) => !BIG_RELAY_URLS.includes(url)).slice(0, 4)
: relayList.write.slice(0, 4)
} else if (!relayUrls.length && !alreadyFetchedFromBigRelays) {
relayUrls = BIG_RELAY_URLS relayUrls = BIG_RELAY_URLS
} }
if (!relayUrls.length) return if (!relayUrls.length) return
@ -1691,50 +1674,21 @@ class ClientService extends EventTarget {
} }
async generateSubRequestsForPubkeys(pubkeys: string[], myPubkey?: string | null) { 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 // If many websocket connections are initiated simultaneously, it will be
// very slow on Safari (for unknown reason) // very slow on Safari (for unknown reason)
if (isSafari()) { 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 } }] return [{ urls, filter: { authors: pubkeys } }]
} }
const relayLists = await this.fetchRelayLists(pubkeys) // Simplified: Use user's relays for all followed users instead of individual relay lists
const group: Record<string, Set<string>> = {} return [{ urls, filter: { authors: pubkeys } }]
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<string, number>()
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) }
}))
} }
} }

14
src/services/lightning.service.ts

@ -47,9 +47,9 @@ class LightningService {
? { recipient: recipientOrEvent } ? { recipient: recipientOrEvent }
: { recipient: recipientOrEvent.pubkey, event: 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.fetchProfile(recipient, true),
client.fetchRelayList(recipient),
sender sender
? client.fetchRelayList(sender) ? client.fetchRelayList(sender)
: Promise.resolve({ read: BIG_RELAY_URLS, write: BIG_RELAY_URLS }) : Promise.resolve({ read: BIG_RELAY_URLS, write: BIG_RELAY_URLS })
@ -66,10 +66,8 @@ class LightningService {
const zapRequestDraft = makeZapRequest({ const zapRequestDraft = makeZapRequest({
...(event ? { event } : { pubkey: recipient }), ...(event ? { event } : { pubkey: recipient }),
amount, amount,
relays: receiptRelayList.read // Privacy: Only use sender's relays + defaults, not recipient's relays
.slice(0, 4) relays: senderRelayList.write.slice(0, 4).concat(BIG_RELAY_URLS),
.concat(senderRelayList.write.slice(0, 3))
.concat(BIG_RELAY_URLS),
comment comment
}) })
const zapRequest = await client.signer.signEvent(zapRequestDraft) const zapRequest = await client.signer.signEvent(zapRequestDraft)
@ -175,8 +173,8 @@ class LightningService {
if (this.recentSupportersCache) { if (this.recentSupportersCache) {
return this.recentSupportersCache return this.recentSupportersCache
} }
const relayList = await client.fetchRelayList(CODY_PUBKEY) // Privacy: Use defaults instead of fetching CODY_PUBKEY's relays
const events = await client.fetchEvents(relayList.read.slice(0, 4), { const events = await client.fetchEvents(BIG_RELAY_URLS.slice(0, 4), {
authors: ['79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432'], // alby authors: ['79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432'], // alby
kinds: [kinds.Zap], kinds: [kinds.Zap],
'#p': OFFICIAL_PUBKEYS, '#p': OFFICIAL_PUBKEYS,

3
src/services/note-stats.service.ts

@ -35,8 +35,9 @@ class NoteStatsService {
if (oldStats?.updatedAt) { if (oldStats?.updatedAt) {
since = 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([ const [relayList, authorProfile] = await Promise.all([
client.fetchRelayList(event.pubkey), pubkey ? client.fetchRelayList(pubkey) : Promise.resolve({ write: [], read: [] }),
client.fetchProfile(event.pubkey) client.fetchProfile(event.pubkey)
]) ])

Loading…
Cancel
Save