Browse Source

fix relay selection when writing

imwald
Silberengel 4 months ago
parent
commit
a05ba5d92a
  1. 67
      src/components/PostEditor/PostRelaySelector.tsx
  2. 97
      src/services/relay-selection.service.ts

67
src/components/PostEditor/PostRelaySelector.tsx

@ -1,8 +1,11 @@
import { simplifyUrl, isLocalNetworkUrl } from '@/lib/url' import { simplifyUrl, isLocalNetworkUrl, normalizeUrl } from '@/lib/url'
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider' import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { getRelayListFromEvent } from '@/lib/event-metadata'
import indexedDb from '@/services/indexed-db.service'
import { ExtendedKind } from '@/constants'
import { Check, ChevronDown, Server } from 'lucide-react' import { Check, ChevronDown, Server } from 'lucide-react'
import { NostrEvent } from 'nostr-tools' import { NostrEvent } from 'nostr-tools'
import { Dispatch, SetStateAction, useCallback, useEffect, useState, useMemo } from 'react' import { Dispatch, SetStateAction, useCallback, useEffect, useState, useMemo } from 'react'
@ -78,8 +81,37 @@ export default function PostRelaySelector({
const updateRelaySelection = async () => { const updateRelaySelection = async () => {
setIsLoading(true) setIsLoading(true)
try { try {
// Ensure cache relays (kind 10432) are included in userWriteRelays even if relayList hasn't been updated yet
// Get cache relays directly from IndexedDB (don't fetch new every time)
let userWriteRelays = relayList?.write || []
if (pubkey) {
try {
const cacheRelayListEvent = await indexedDb.getReplaceableEvent(pubkey, ExtendedKind.CACHE_RELAYS)
if (cacheRelayListEvent) {
const cacheRelayList = getRelayListFromEvent(cacheRelayListEvent)
// Get all cache relays (they should all be local network URLs)
// Include both write and both-scoped relays (cache relays should be write-capable)
const cacheRelays = [
...cacheRelayList.write,
...cacheRelayList.originalRelays
.filter(relay => (relay.scope === 'both' || relay.scope === 'write') && isLocalNetworkUrl(relay.url))
.map(relay => relay.url)
].filter(url => isLocalNetworkUrl(url))
const existingUrls = new Set(userWriteRelays.map(url => normalizeUrl(url) || url))
const newCacheRelays = cacheRelays
.map(url => normalizeUrl(url) || url)
.filter((url): url is string => !!url && !existingUrls.has(url))
if (newCacheRelays.length > 0) {
userWriteRelays = [...newCacheRelays, ...userWriteRelays]
}
}
} catch (error) {
logger.warn('Failed to get cache relays from IndexedDB', { error, pubkey })
}
}
const result = await relaySelectionService.selectRelays({ const result = await relaySelectionService.selectRelays({
userWriteRelays: relayList?.write || [], userWriteRelays,
userReadRelays: relayList?.read || [], userReadRelays: relayList?.read || [],
favoriteRelays: memoizedFavoriteRelays, favoriteRelays: memoizedFavoriteRelays,
blockedRelays: memoizedBlockedRelays, blockedRelays: memoizedBlockedRelays,
@ -140,8 +172,37 @@ export default function PostRelaySelector({
const updateRelaySelection = async () => { const updateRelaySelection = async () => {
setIsLoading(true) setIsLoading(true)
try { try {
// Ensure cache relays (kind 10432) are included in userWriteRelays even if relayList hasn't been updated yet
// Get cache relays directly from IndexedDB (don't fetch new every time)
let userWriteRelays = relayList?.write || []
if (pubkey) {
try {
const cacheRelayListEvent = await indexedDb.getReplaceableEvent(pubkey, ExtendedKind.CACHE_RELAYS)
if (cacheRelayListEvent) {
const cacheRelayList = getRelayListFromEvent(cacheRelayListEvent)
// Get all cache relays (they should all be local network URLs)
// Include both write and both-scoped relays (cache relays should be write-capable)
const cacheRelays = [
...cacheRelayList.write,
...cacheRelayList.originalRelays
.filter(relay => (relay.scope === 'both' || relay.scope === 'write') && isLocalNetworkUrl(relay.url))
.map(relay => relay.url)
].filter(url => isLocalNetworkUrl(url))
const existingUrls = new Set(userWriteRelays.map(url => normalizeUrl(url) || url))
const newCacheRelays = cacheRelays
.map(url => normalizeUrl(url) || url)
.filter((url): url is string => !!url && !existingUrls.has(url))
if (newCacheRelays.length > 0) {
userWriteRelays = [...newCacheRelays, ...userWriteRelays]
}
}
} catch (error) {
logger.warn('Failed to get cache relays from IndexedDB', { error, pubkey })
}
}
const result = await relaySelectionService.selectRelays({ const result = await relaySelectionService.selectRelays({
userWriteRelays: relayList?.write || [], userWriteRelays,
userReadRelays: relayList?.read || [], userReadRelays: relayList?.read || [],
favoriteRelays: memoizedFavoriteRelays, favoriteRelays: memoizedFavoriteRelays,
blockedRelays: memoizedBlockedRelays, blockedRelays: memoizedBlockedRelays,

97
src/services/relay-selection.service.ts

@ -3,8 +3,10 @@ import { ExtendedKind } from '@/constants'
import { FAST_WRITE_RELAY_URLS } from '@/constants' import { FAST_WRITE_RELAY_URLS } from '@/constants'
import client from '@/services/client.service' import client from '@/services/client.service'
import { normalizeUrl, isLocalNetworkUrl } from '@/lib/url' import { normalizeUrl, isLocalNetworkUrl } from '@/lib/url'
import { TRelaySet } from '@/types' import { TRelaySet, TRelayList } from '@/types'
import logger from '@/lib/logger' import logger from '@/lib/logger'
import indexedDb from '@/services/indexed-db.service'
import { getRelayListFromEvent } from '@/lib/event-metadata'
export interface RelaySelectionContext { export interface RelaySelectionContext {
// User's own relays // User's own relays
@ -96,6 +98,11 @@ class RelaySelectionService {
const userRelays = userWriteRelays.length > 0 ? userWriteRelays : FAST_WRITE_RELAY_URLS const userRelays = userWriteRelays.length > 0 ? userWriteRelays : FAST_WRITE_RELAY_URLS
userRelays.forEach(addRelay) userRelays.forEach(addRelay)
// Explicitly ensure cache relays (local network URLs) are included in selectable relays
// This ensures they show up even if there's a timing issue with relay list updates
const cacheRelays = userWriteRelays.filter(url => isLocalNetworkUrl(url))
cacheRelays.forEach(addRelay)
// Always include favorite relays // Always include favorite relays
favoriteRelays.forEach(addRelay) favoriteRelays.forEach(addRelay)
@ -120,6 +127,72 @@ class RelaySelectionService {
return this.filterBlockedRelays(deduplicatedRelays, context.blockedRelays) return this.filterBlockedRelays(deduplicatedRelays, context.blockedRelays)
} }
/**
* Get relay list from IndexedDB cache (kind 10002 and 10432 merged)
* If not in cache, fetch from relays before returning empty
* This avoids fetching from relays every time, but ensures we have data when needed
*/
private async getCachedRelayList(pubkey: string): Promise<TRelayList | null> {
try {
// Get both kind 10002 (relay list) and kind 10432 (cache relays) from IndexedDB
const [relayListEvent, cacheRelayListEvent] = await Promise.all([
indexedDb.getReplaceableEvent(pubkey, kinds.RelayList),
indexedDb.getReplaceableEvent(pubkey, ExtendedKind.CACHE_RELAYS)
])
let relayList: TRelayList
// If no cached relay list event, fetch from relays (which will also cache it)
if (!relayListEvent) {
try {
relayList = await client.fetchRelayList(pubkey)
} catch (error) {
logger.warn('Failed to fetch relay list from relays', { error, pubkey })
relayList = {
write: [],
read: [],
originalRelays: []
}
}
} else {
relayList = getRelayListFromEvent(relayListEvent)
}
// Merge cache relays (kind 10432) into the relay list
if (cacheRelayListEvent) {
const cacheRelayList = getRelayListFromEvent(cacheRelayListEvent)
// Merge read relays - cache relays first, then others
const mergedRead = [...cacheRelayList.read, ...relayList.read]
const mergedWrite = [...cacheRelayList.write, ...relayList.write]
const mergedOriginalRelays = new Map<string, { url: string; scope: 'read' | 'write' | 'both' }>()
// Add cache relay original relays first (prioritized)
cacheRelayList.originalRelays.forEach(relay => {
mergedOriginalRelays.set(relay.url, relay)
})
// Then add regular relay original relays
relayList.originalRelays.forEach(relay => {
if (!mergedOriginalRelays.has(relay.url)) {
mergedOriginalRelays.set(relay.url, relay)
}
})
// Deduplicate while preserving order (cache relays first)
return {
write: Array.from(new Set(mergedWrite)),
read: Array.from(new Set(mergedRead)),
originalRelays: Array.from(mergedOriginalRelays.values())
}
}
return relayList
} catch (error) {
logger.warn('Failed to get cached relay list from IndexedDB', { error, pubkey })
return null
}
}
/** /**
* Get contextual relays based on the type of post * Get contextual relays based on the type of post
*/ */
@ -132,8 +205,9 @@ class RelaySelectionService {
// For replies (any kind) and public messages // For replies (any kind) and public messages
if (parentEvent || isPublicMessage) { if (parentEvent || isPublicMessage) {
// Get the replied-to author's read relays (filter out their local relays) // Get the replied-to author's read relays (filter out their local relays)
// Use cached version from IndexedDB instead of fetching from relays
if (parentEvent) { if (parentEvent) {
const authorRelayList = await client.fetchRelayList(parentEvent.pubkey) const authorRelayList = await this.getCachedRelayList(parentEvent.pubkey)
if (authorRelayList?.read) { if (authorRelayList?.read) {
const filteredRelays = this.filterLocalRelaysFromOthers(authorRelayList.read) const filteredRelays = this.filterLocalRelaysFromOthers(authorRelayList.read)
filteredRelays.slice(0, 4).forEach(url => contextualRelays.add(url)) filteredRelays.slice(0, 4).forEach(url => contextualRelays.add(url))
@ -168,14 +242,16 @@ class RelaySelectionService {
const mentionRelayLists = await Promise.all( const mentionRelayLists = await Promise.all(
mentionedPubkeys.map(async (pubkey) => { mentionedPubkeys.map(async (pubkey) => {
try { try {
const relayList = await client.fetchRelayList(pubkey) // Use cached version from IndexedDB instead of fetching from relays
const relayList = await this.getCachedRelayList(pubkey)
if (!relayList) return []
// Use write relays for replies, read relays for public messages // Use write relays for replies, read relays for public messages
const relayType = isPublicMessage ? 'read' : 'write' const relayType = isPublicMessage ? 'read' : 'write'
const userRelays = relayList?.[relayType] || [] const userRelays = relayList[relayType] || []
// Filter out local relays from other users // Filter out local relays from other users
return this.filterLocalRelaysFromOthers(userRelays) return this.filterLocalRelaysFromOthers(userRelays)
} catch (error) { } catch (error) {
logger.warn('Failed to fetch relay list', { pubkey, error }) logger.warn('Failed to get cached relay list', { pubkey, error })
return [] return []
} }
}) })
@ -254,12 +330,14 @@ class RelaySelectionService {
const mentionRelayLists = await Promise.all( const mentionRelayLists = await Promise.all(
mentionedPubkeys.map(async (pubkey) => { mentionedPubkeys.map(async (pubkey) => {
try { try {
const relayList = await client.fetchRelayList(pubkey) // Use cached version from IndexedDB instead of fetching from relays
const userRelays = relayList?.write || [] const relayList = await this.getCachedRelayList(pubkey)
if (!relayList) return []
const userRelays = relayList.write || []
// Filter out local relays from other users // Filter out local relays from other users
return this.filterLocalRelaysFromOthers(userRelays) return this.filterLocalRelaysFromOthers(userRelays)
} catch (error) { } catch (error) {
logger.warn('Failed to fetch relay list', { pubkey, error }) logger.warn('Failed to get cached relay list', { pubkey, error })
return [] return []
} }
}) })
@ -342,8 +420,9 @@ class RelaySelectionService {
} }
} else if (parentEvent && parentEvent.kind === ExtendedKind.PUBLIC_MESSAGE) { } else if (parentEvent && parentEvent.kind === ExtendedKind.PUBLIC_MESSAGE) {
// For public message replies, get original sender's read relays (filter out their local relays) // For public message replies, get original sender's read relays (filter out their local relays)
// Use cached version from IndexedDB instead of fetching from relays
try { try {
const senderRelayList = await client.fetchRelayList(parentEvent.pubkey) const senderRelayList = await this.getCachedRelayList(parentEvent.pubkey)
if (senderRelayList?.read) { if (senderRelayList?.read) {
const filteredRelays = this.filterLocalRelaysFromOthers(senderRelayList.read) const filteredRelays = this.filterLocalRelaysFromOthers(senderRelayList.read)
filteredRelays.forEach(url => { filteredRelays.forEach(url => {

Loading…
Cancel
Save