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. 90
      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 @@ @@ -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 @@ -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
}

3
src/components/NoteStats/LikeButton.tsx

@ -80,8 +80,7 @@ export default function LikeButton({ event }: { event: Event }) { @@ -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)

3
src/components/NoteStats/Likes.tsx

@ -45,8 +45,7 @@ export default function Likes({ event }: { event: Event }) { @@ -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)

9
src/components/NoteStats/VoteButtons.tsx

@ -61,22 +61,19 @@ export default function VoteButtons({ event }: { event: Event }) { @@ -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) {

11
src/components/PostEditor/PostRelaySelector.tsx

@ -51,15 +51,10 @@ export default function PostRelaySelector({ @@ -51,15 +51,10 @@ export default function PostRelaySelector({
const { relayUrls } = useCurrentRelays()
const { relaySets, favoriteRelays } = useFavoriteRelays()
const [postTargetItems, setPostTargetItems] = useState<TPostTargetItem[]>([])
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')

18
src/components/Profile/ProfileFeed.tsx

@ -40,27 +40,26 @@ export default function ProfileFeed({ @@ -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({ @@ -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({ @@ -81,7 +79,7 @@ export default function ProfileFeed({
])
}
init()
}, [pubkey, listMode])
}, [pubkey, listMode, myPubkey])
const handleListModeChange = (mode: TNoteListMode) => {
setListMode(mode)

9
src/components/QuoteList/index.tsx

@ -29,14 +29,9 @@ export default function QuoteList({ event, className }: { event: Event; classNam @@ -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(
[

15
src/components/ReplyNoteList/index.tsx

@ -219,20 +219,11 @@ export default function ReplyNoteList({ index, event, sort = 'oldest' }: { index @@ -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<Filter, 'since' | 'until'> & {

7
src/providers/NostrProvider/index.tsx

@ -655,11 +655,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -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)

90
src/services/client.service.ts

@ -7,7 +7,7 @@ import { @@ -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 { @@ -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 { @@ -124,6 +119,7 @@ class ClientService extends EventTarget {
})
}
}
if (
[
kinds.RelayList,
@ -136,7 +132,8 @@ class ClientService extends EventTarget { @@ -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 { @@ -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 { @@ -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 { @@ -1061,7 +1051,6 @@ class ClientService extends EventTarget {
private async _fetchEvent(id: string): Promise<NEvent | undefined> {
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -1691,50 +1674,21 @@ class ClientService extends EventTarget {
}
async generateSubRequestsForPubkeys(pubkeys: string[], myPubkey?: string | null) {
// If many websocket connections are initiated simultaneously, it will be
// very slow on Safari (for unknown reason)
if (isSafari()) {
// 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)
}
return [{ urls, filter: { authors: pubkeys } }]
}
const relayLists = await this.fetchRelayLists(pubkeys)
const group: Record<string, Set<string>> = {}
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)
})
// If many websocket connections are initiated simultaneously, it will be
// very slow on Safari (for unknown reason)
if (isSafari()) {
return [{ urls, filter: { authors: pubkeys } }]
}
})
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 } }]
}
}

14
src/services/lightning.service.ts

@ -47,9 +47,9 @@ class LightningService { @@ -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 { @@ -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 { @@ -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,

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

@ -35,8 +35,9 @@ class NoteStatsService { @@ -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)
])

Loading…
Cancel
Save