diff --git a/src/App.tsx b/src/App.tsx index ea47a231..8e407b90 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,8 @@ import { useOnlineStatus } from './hooks/useOnlineStatus' import { RELAYS } from './config/relays' import { SkeletonThemeProvider } from './components/Skeletons' import { DebugBus } from './utils/debugBus' +import { loadUserRelayList, loadBlockedRelays, computeRelaySet } from './services/relayListService' +import { applyRelaySetToPool, getActiveRelayUrls, ALWAYS_LOCAL_RELAYS } from './services/relayManager' import { Bookmark } from './types/bookmarks' import { bookmarkController } from './services/bookmarkController' import { contactsController } from './services/contactsController' @@ -602,6 +604,86 @@ function App() { } }) + // Handle user relay list and blocked relays when account changes + const userRelaysSub = accounts.active$.subscribe(async (account) => { + if (account) { + // User logged in - load their relay list and apply it + try { + const pubkey = account.pubkey + + // Load user's relay list (10002) and blocked relays (10006) in parallel + const [userRelayList, blockedRelays] = await Promise.all([ + loadUserRelayList(pool, pubkey), + loadBlockedRelays(pool, pubkey) + ]) + + // Get bunker relays if this is a nostr-connect account + let bunkerRelays: string[] = [] + if (account.type === 'nostr-connect') { + const nostrConnectAccount = account as Accounts.NostrConnectAccount + const signerData = nostrConnectAccount.toJSON().signer + bunkerRelays = signerData.relays || [] + } + + // Compute final relay set + const finalRelays = computeRelaySet({ + hardcoded: RELAYS, + bunker: bunkerRelays, + userList: userRelayList, + blocked: blockedRelays, + alwaysIncludeLocal: ALWAYS_LOCAL_RELAYS + }) + + // Apply to pool + applyRelaySetToPool(pool, finalRelays) + + // Update keep-alive subscription with new relay set + const poolWithSub = pool as unknown as { _keepAliveSubscription?: { unsubscribe: () => void } } + if (poolWithSub._keepAliveSubscription) { + poolWithSub._keepAliveSubscription.unsubscribe() + } + const activeRelays = getActiveRelayUrls(pool) + const newKeepAliveSub = pool.subscription(activeRelays, { kinds: [0], limit: 0 }).subscribe({ + next: () => {}, + error: (err) => console.warn('Keep-alive subscription error:', err) + }) + poolWithSub._keepAliveSubscription = newKeepAliveSub + + // Update address loader with new relays + const addressLoader = createAddressLoader(pool, { + eventStore: store, + lookupRelays: activeRelays + }) + store.addressableLoader = addressLoader + store.replaceableLoader = addressLoader + } catch (error) { + console.error('Failed to load user relays:', error) + } + } else { + // User logged out - reset to hardcoded relays + applyRelaySetToPool(pool, RELAYS) + + // Update keep-alive subscription + const poolWithSub = pool as unknown as { _keepAliveSubscription?: { unsubscribe: () => void } } + if (poolWithSub._keepAliveSubscription) { + poolWithSub._keepAliveSubscription.unsubscribe() + } + const newKeepAliveSub = pool.subscription(RELAYS, { kinds: [0], limit: 0 }).subscribe({ + next: () => {}, + error: (err) => console.warn('Keep-alive subscription error:', err) + }) + poolWithSub._keepAliveSubscription = newKeepAliveSub + + // Reset address loader + const addressLoader = createAddressLoader(pool, { + eventStore: store, + lookupRelays: RELAYS + }) + store.addressableLoader = addressLoader + store.replaceableLoader = addressLoader + } + }) + // Keep all relay connections alive indefinitely by creating a persistent subscription // This prevents disconnection when no other subscriptions are active // Create a minimal subscription that never completes to keep connections alive @@ -630,6 +712,7 @@ function App() { accountsSub.unsubscribe() activeSub.unsubscribe() bunkerReconnectSub.unsubscribe() + userRelaysSub.unsubscribe() // Clean up keep-alive subscription if it exists const poolWithSub = pool as unknown as { _keepAliveSubscription?: { unsubscribe: () => void } } if (poolWithSub._keepAliveSubscription) { diff --git a/src/components/BookmarkList.tsx b/src/components/BookmarkList.tsx index bac395cf..a031642d 100644 --- a/src/components/BookmarkList.tsx +++ b/src/components/BookmarkList.tsx @@ -17,8 +17,8 @@ import { groupIndividualBookmarks, hasContent, getBookmarkSets, getBookmarksWith import { UserSettings } from '../services/settingsService' import AddBookmarkModal from './AddBookmarkModal' import { createWebBookmark } from '../services/webBookmarkService' -import { RELAYS } from '../config/relays' import { Hooks } from 'applesauce-react' +import { getActiveRelayUrls } from '../services/relayManager' import BookmarkFilters, { BookmarkFilterType } from './BookmarkFilters' import { filterBookmarksByType } from '../utils/bookmarkTypeClassifier' import LoginOptions from './LoginOptions' @@ -125,7 +125,7 @@ export const BookmarkList: React.FC = ({ throw new Error('Please login to create bookmarks') } - await createWebBookmark(url, title, description, tags, activeAccount, relayPool, RELAYS) + await createWebBookmark(url, title, description, tags, activeAccount, relayPool, getActiveRelayUrls(relayPool)) } // Pull-to-refresh for bookmarks diff --git a/src/components/ContentPanel.tsx b/src/components/ContentPanel.tsx index e0f02304..7bac242b 100644 --- a/src/components/ContentPanel.tsx +++ b/src/components/ContentPanel.tsx @@ -10,8 +10,8 @@ import { faSpinner, faCheckCircle, faEllipsisH, faExternalLinkAlt, faMobileAlt, import { ContentSkeleton } from './Skeletons' import { nip19 } from 'nostr-tools' import { getNostrUrl, getSearchUrl } from '../config/nostrGateways' -import { RELAYS } from '../config/relays' import { RelayPool } from 'applesauce-relay' +import { getActiveRelayUrls } from '../services/relayManager' import { IAccount } from 'applesauce-accounts' import { NostrEvent } from 'nostr-tools' import { Highlight } from '../types/highlights' @@ -357,7 +357,8 @@ const ContentPanel: React.FC = ({ if (!currentArticle) return null const dTag = currentArticle.tags.find(t => t[0] === 'd')?.[1] || '' - const relayHints = RELAYS.filter(r => + const activeRelays = relayPool ? getActiveRelayUrls(relayPool) : [] + const relayHints = activeRelays.filter(r => !r.includes('localhost') && !r.includes('127.0.0.1') ).slice(0, 3) diff --git a/src/components/HighlightItem.tsx b/src/components/HighlightItem.tsx index d85a6c9e..733d270d 100644 --- a/src/components/HighlightItem.tsx +++ b/src/components/HighlightItem.tsx @@ -8,8 +8,8 @@ import { Models, IEventStore } from 'applesauce-core' import { RelayPool } from 'applesauce-relay' import { Hooks } from 'applesauce-react' import { onSyncStateChange, isEventSyncing } from '../services/offlineSyncService' -import { RELAYS } from '../config/relays' import { areAllRelaysLocal } from '../utils/helpers' +import { getActiveRelayUrls } from '../services/relayManager' import { nip19 } from 'nostr-tools' import { formatDateCompact } from '../utils/bookmarkUtils' import { createDeletionRequest } from '../services/deletionService' @@ -150,10 +150,10 @@ export const HighlightItem: React.FC = ({ setShowOfflineIndicator(false) // Update the highlight with all relays after successful sync - if (onHighlightUpdate && highlight.isLocalOnly) { + if (onHighlightUpdate && highlight.isLocalOnly && relayPool) { const updatedHighlight = { ...highlight, - publishedRelays: RELAYS, + publishedRelays: getActiveRelayUrls(relayPool), isLocalOnly: false, isOfflineCreated: false } @@ -164,7 +164,7 @@ export const HighlightItem: React.FC = ({ }) return unsubscribe - }, [highlight, onHighlightUpdate]) + }, [highlight, onHighlightUpdate, relayPool]) useEffect(() => { if (isSelected && itemRef.current) { @@ -224,7 +224,8 @@ export const HighlightItem: React.FC = ({ const getHighlightLinks = () => { // Encode the highlight event itself (kind 9802) as a nevent // Get non-local relays for the hint - const relayHints = RELAYS.filter(r => + const activeRelays = relayPool ? getActiveRelayUrls(relayPool) : [] + const relayHints = activeRelays.filter(r => !r.includes('localhost') && !r.includes('127.0.0.1') ).slice(0, 3) // Include up to 3 relay hints @@ -260,7 +261,7 @@ export const HighlightItem: React.FC = ({ } // Publish to all configured relays - let the relay pool handle connection state - const targetRelays = RELAYS + const targetRelays = getActiveRelayUrls(relayPool) await relayPool.publish(targetRelays, event) @@ -328,7 +329,8 @@ export const HighlightItem: React.FC = ({ } // Fallback: show all relays we queried (where this was likely fetched from) - const relayNames = RELAYS.map(url => + const activeRelays = relayPool ? getActiveRelayUrls(relayPool) : [] + const relayNames = activeRelays.map(url => url.replace(/^wss?:\/\//, '').replace(/\/$/, '') ) return { diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index 12fce2b0..2c4dd659 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -8,8 +8,8 @@ import { useNavigate } from 'react-router-dom' import { HighlightItem } from './HighlightItem' import { BlogPostPreview, fetchBlogPostsFromAuthors } from '../services/exploreService' import { fetchHighlights } from '../services/highlightService' -import { RELAYS } from '../config/relays' import { KINDS } from '../config/kinds' +import { getActiveRelayUrls } from '../services/relayManager' import AuthorCard from './AuthorCard' import BlogPostCard from './BlogPostCard' import { BlogPostSkeleton, HighlightSkeleton } from './Skeletons' @@ -109,7 +109,7 @@ const Profile: React.FC = ({ }) // Fetch writings in background (no limit for single user profile) - fetchBlogPostsFromAuthors(relayPool, [pubkey], RELAYS, undefined, null) + fetchBlogPostsFromAuthors(relayPool, [pubkey], getActiveRelayUrls(relayPool), undefined, null) .then(writings => { writings.forEach(w => eventStore.add(w.event)) }) diff --git a/src/services/archiveController.ts b/src/services/archiveController.ts index 689e0b5b..4a9802ab 100644 --- a/src/services/archiveController.ts +++ b/src/services/archiveController.ts @@ -3,7 +3,6 @@ import { IEventStore } from 'applesauce-core' import { NostrEvent } from 'nostr-tools' import { queryEvents } from './dataFetch' import { KINDS } from '../config/kinds' -import { RELAYS } from '../config/relays' import { ARCHIVE_EMOJI } from './reactionService' import { nip19 } from 'nostr-tools' @@ -124,8 +123,8 @@ class ArchiveController { try { // Stream kind:17 and kind:7 in parallel const [kind17, kind7] = await Promise.all([ - queryEvents(relayPool, { kinds: [17], authors: [pubkey] }, { relayUrls: RELAYS, onEvent: handleUrlReaction }), - queryEvents(relayPool, { kinds: [7], authors: [pubkey] }, { relayUrls: RELAYS, onEvent: handleEventReaction }) + queryEvents(relayPool, { kinds: [17], authors: [pubkey] }, { onEvent: handleUrlReaction }), + queryEvents(relayPool, { kinds: [7], authors: [pubkey] }, { onEvent: handleEventReaction }) ]) if (startGen !== this.generation) return @@ -138,7 +137,7 @@ class ArchiveController { if (this.pendingEventIds.size > 0) { // Fetch referenced articles (kind:30023) and map event IDs to naddr const ids = Array.from(this.pendingEventIds) - const articleEvents = await queryEvents(relayPool, { kinds: [KINDS.BlogPost], ids }, { relayUrls: RELAYS }) + const articleEvents = await queryEvents(relayPool, { kinds: [KINDS.BlogPost], ids }) console.log('[archive] fetched articles for mapping:', articleEvents.length) for (const article of articleEvents) { const dTag = article.tags.find(t => t[0] === 'd')?.[1] diff --git a/src/services/libraryService.ts b/src/services/libraryService.ts index 5d8bed87..640f7fa9 100644 --- a/src/services/libraryService.ts +++ b/src/services/libraryService.ts @@ -1,7 +1,6 @@ import { RelayPool } from 'applesauce-relay' import { NostrEvent } from 'nostr-tools' import { Helpers } from 'applesauce-core' -import { RELAYS } from '../config/relays' import { KINDS } from '../config/kinds' import { ARCHIVE_EMOJI } from './reactionService' import { BlogPostPreview } from './exploreService' @@ -30,8 +29,8 @@ export async function fetchReadArticles( try { // Fetch kind:7 and kind:17 reactions in parallel const [kind7Events, kind17Events] = await Promise.all([ - queryEvents(relayPool, { kinds: [KINDS.ReactionToEvent], authors: [userPubkey] }, { relayUrls: RELAYS }), - queryEvents(relayPool, { kinds: [KINDS.ReactionToUrl], authors: [userPubkey] }, { relayUrls: RELAYS }) + queryEvents(relayPool, { kinds: [KINDS.ReactionToEvent], authors: [userPubkey] }), + queryEvents(relayPool, { kinds: [KINDS.ReactionToUrl], authors: [userPubkey] }) ]) const readArticles: ReadArticle[] = [] @@ -115,8 +114,7 @@ export async function fetchReadArticlesWithData( const articleEvents = await queryEvents( relayPool, - { kinds: [KINDS.BlogPost], ids: eventIds }, - { relayUrls: RELAYS } + { kinds: [KINDS.BlogPost], ids: eventIds } ) // Deduplicate article events by ID diff --git a/src/services/reactionService.ts b/src/services/reactionService.ts index 1285a2c9..17ea041a 100644 --- a/src/services/reactionService.ts +++ b/src/services/reactionService.ts @@ -2,8 +2,8 @@ import { RelayPool, completeOnEose, onlyEvents } from 'applesauce-relay' import { IAccount } from 'applesauce-accounts' import { NostrEvent } from 'nostr-tools' import { lastValueFrom, takeUntil, timer, toArray } from 'rxjs' -import { RELAYS } from '../config/relays' import { EventFactory } from 'applesauce-factory' +import { getActiveRelayUrls } from './relayManager' const ARCHIVE_EMOJI = '📚' @@ -49,7 +49,7 @@ export async function createEventReaction( // Publish to relays - await relayPool.publish(RELAYS, signed) + await relayPool.publish(getActiveRelayUrls(relayPool), signed) return signed @@ -99,7 +99,7 @@ export async function createWebsiteReaction( // Publish to relays - await relayPool.publish(RELAYS, signed) + await relayPool.publish(getActiveRelayUrls(relayPool), signed) return signed @@ -122,7 +122,7 @@ export async function deleteReaction( created_at: Math.floor(Date.now() / 1000) })) const signed = await factory.sign(draft) - await relayPool.publish(RELAYS, signed) + await relayPool.publish(getActiveRelayUrls(relayPool), signed) return signed } @@ -146,7 +146,7 @@ export async function hasMarkedEventAsRead( } const events$ = relayPool - .req(RELAYS, filter) + .req(getActiveRelayUrls(relayPool), filter) .pipe( onlyEvents(), completeOnEose(), @@ -199,7 +199,7 @@ export async function hasMarkedWebsiteAsRead( } const events$ = relayPool - .req(RELAYS, filter) + .req(getActiveRelayUrls(relayPool), filter) .pipe( onlyEvents(), completeOnEose(), diff --git a/src/services/readingProgressController.ts b/src/services/readingProgressController.ts index 25031e64..b723eb9a 100644 --- a/src/services/readingProgressController.ts +++ b/src/services/readingProgressController.ts @@ -3,7 +3,6 @@ import { IEventStore } from 'applesauce-core' import { NostrEvent } from 'nostr-tools' import { queryEvents } from './dataFetch' import { KINDS } from '../config/kinds' -import { RELAYS } from '../config/relays' import { processReadingProgress } from './readingDataProcessor' import { ReadItem } from './readsService' import { ARCHIVE_EMOJI } from './reactionService' @@ -233,7 +232,7 @@ class ReadingProgressController { queryEvents(relayPool, { kinds: [KINDS.ReadingProgress], authors: [pubkey] - }, { relayUrls: RELAYS }) + }) .then((relayEvents) => { if (startGeneration !== this.generation) return console.log('[readingProgress] Got reading progress from relays:', relayEvents.length) @@ -343,8 +342,8 @@ class ReadingProgressController { // Fire queries with onEvent callbacks for streaming behavior const [kind17Events, kind7Events] = await Promise.all([ - queryEvents(relayPool, { kinds: [17], authors: [pubkey] }, { relayUrls: RELAYS, onEvent: handleUrlReaction }), - queryEvents(relayPool, { kinds: [7], authors: [pubkey] }, { relayUrls: RELAYS, onEvent: handleEventReaction }) + queryEvents(relayPool, { kinds: [17], authors: [pubkey] }, { onEvent: handleUrlReaction }), + queryEvents(relayPool, { kinds: [7], authors: [pubkey] }, { onEvent: handleEventReaction }) ]) if (generation !== this.generation) return @@ -356,7 +355,7 @@ class ReadingProgressController { if (pendingEventIds.size > 0) { // Fetch referenced 30023 events, streaming not required here const ids = Array.from(pendingEventIds) - const articleEvents = await queryEvents(relayPool, { kinds: [KINDS.BlogPost], ids }, { relayUrls: RELAYS }) + const articleEvents = await queryEvents(relayPool, { kinds: [KINDS.BlogPost], ids }) const eventIdToNaddr = new Map() for (const article of articleEvents) { const dTag = article.tags.find(t => t[0] === 'd')?.[1] diff --git a/src/services/readsService.ts b/src/services/readsService.ts index 8aa63b0a..28fd0696 100644 --- a/src/services/readsService.ts +++ b/src/services/readsService.ts @@ -3,7 +3,6 @@ import { Helpers } from 'applesauce-core' import { Bookmark } from '../types/bookmarks' import { fetchReadArticles } from './libraryService' import { queryEvents } from './dataFetch' -import { RELAYS } from '../config/relays' import { KINDS } from '../config/kinds' import { classifyBookmarkType } from '../utils/bookmarkTypeClassifier' import { nip19 } from 'nostr-tools' @@ -44,7 +43,7 @@ export async function fetchAllReads( try { // Fetch all data sources in parallel const [progressEvents, markedAsReadArticles] = await Promise.all([ - queryEvents(relayPool, { kinds: [KINDS.ReadingProgress], authors: [userPubkey] }, { relayUrls: RELAYS }), + queryEvents(relayPool, { kinds: [KINDS.ReadingProgress], authors: [userPubkey] }), fetchReadArticles(relayPool, userPubkey) ]) @@ -130,8 +129,7 @@ export async function fetchAllReads( const events = await queryEvents( relayPool, - { kinds: [KINDS.BlogPost], authors, '#d': identifiers }, - { relayUrls: RELAYS } + { kinds: [KINDS.BlogPost], authors, '#d': identifiers } ) // Merge event data into ReadItems and emit diff --git a/src/services/relayListService.ts b/src/services/relayListService.ts new file mode 100644 index 00000000..0e284d5a --- /dev/null +++ b/src/services/relayListService.ts @@ -0,0 +1,132 @@ +import { RelayPool } from 'applesauce-relay' +import { NostrEvent } from 'nostr-tools' +import { queryEvents } from './dataFetch' + +export interface UserRelayInfo { + url: string + mode?: 'read' | 'write' | 'both' +} + +/** + * Loads user's relay list from kind 10002 (NIP-65) + */ +export async function loadUserRelayList( + relayPool: RelayPool, + pubkey: string +): Promise { + try { + const events = await queryEvents(relayPool, { + kinds: [10002], + authors: [pubkey] + }) + + if (events.length === 0) return [] + + // Get most recent event + const sortedEvents = events.sort((a, b) => b.created_at - a.created_at) + const relayListEvent = sortedEvents[0] + + const relays: UserRelayInfo[] = [] + for (const tag of relayListEvent.tags) { + if (tag[0] === 'r' && tag[1]) { + const url = tag[1] + const mode = tag[2] as 'read' | 'write' | undefined + relays.push({ + url, + mode: mode || 'both' + }) + } + } + + return relays + } catch (error) { + console.error('Failed to load user relay list:', error) + return [] + } +} + +/** + * Loads blocked relays from kind 10006 (NIP-51 mute list) + */ +export async function loadBlockedRelays( + relayPool: RelayPool, + pubkey: string +): Promise { + try { + const events = await queryEvents(relayPool, { + kinds: [10006], + authors: [pubkey] + }) + + if (events.length === 0) return [] + + // Get most recent event + const sortedEvents = events.sort((a, b) => b.created_at - a.created_at) + const muteListEvent = sortedEvents[0] + + const blocked: string[] = [] + for (const tag of muteListEvent.tags) { + if (tag[0] === 'r' && tag[1]) { + blocked.push(tag[1]) + } + } + + return blocked + } catch (error) { + console.error('Failed to load blocked relays:', error) + return [] + } +} + +/** + * Computes final relay set by merging inputs and removing blocked relays + */ +export function computeRelaySet(params: { + hardcoded: string[] + bunker?: string[] + userList?: UserRelayInfo[] + blocked?: string[] + alwaysIncludeLocal: string[] +}): string[] { + const { + hardcoded, + bunker = [], + userList = [], + blocked = [], + alwaysIncludeLocal + } = params + + const relaySet = new Set() + const blockedSet = new Set(blocked) + + // Helper to check if relay should be included + const shouldInclude = (url: string): boolean => { + // Always include local relays + if (alwaysIncludeLocal.includes(url)) return true + // Otherwise check if blocked + return !blockedSet.has(url) + } + + // Add hardcoded relays + for (const url of hardcoded) { + if (shouldInclude(url)) relaySet.add(url) + } + + // Add bunker relays + for (const url of bunker) { + if (shouldInclude(url)) relaySet.add(url) + } + + // Add user relays (treating 'both' and 'read' as applicable for queries) + for (const relay of userList) { + if (shouldInclude(relay.url)) relaySet.add(relay.url) + } + + // Always ensure local relays are present + for (const url of alwaysIncludeLocal) { + relaySet.add(url) + } + + return Array.from(relaySet) +} + diff --git a/src/services/relayManager.ts b/src/services/relayManager.ts new file mode 100644 index 00000000..f731089d --- /dev/null +++ b/src/services/relayManager.ts @@ -0,0 +1,52 @@ +import { RelayPool } from 'applesauce-relay' +import { prioritizeLocalRelays } from '../utils/helpers' + +/** + * Local relays that are always included + */ +export const ALWAYS_LOCAL_RELAYS = [ + 'ws://localhost:10547', + 'ws://localhost:4869' +] + +/** + * Gets active relay URLs from the relay pool + */ +export function getActiveRelayUrls(relayPool: RelayPool): string[] { + const urls = Array.from(relayPool.relays.keys()) + return prioritizeLocalRelays(urls) +} + +/** + * Applies a new relay set to the pool: adds missing relays, removes extras + */ +export function applyRelaySetToPool( + relayPool: RelayPool, + finalUrls: string[] +): void { + const currentUrls = new Set(Array.from(relayPool.relays.keys())) + const targetUrls = new Set(finalUrls) + + // Add new relays + const toAdd = finalUrls.filter(url => !currentUrls.has(url)) + if (toAdd.length > 0) { + relayPool.group(toAdd) + } + + // Remove relays not in target (but always keep local relays) + const toRemove: string[] = [] + for (const url of currentUrls) { + if (!targetUrls.has(url) && !ALWAYS_LOCAL_RELAYS.includes(url)) { + toRemove.push(url) + } + } + + for (const url of toRemove) { + const relay = relayPool.relays.get(url) + if (relay) { + relay.close() + relayPool.relays.delete(url) + } + } +} + diff --git a/src/services/writeService.ts b/src/services/writeService.ts index 517e8b84..ac7b74f4 100644 --- a/src/services/writeService.ts +++ b/src/services/writeService.ts @@ -1,9 +1,9 @@ import { RelayPool } from 'applesauce-relay' import { NostrEvent } from 'nostr-tools' import { IEventStore } from 'applesauce-core' -import { RELAYS } from '../config/relays' import { isLocalRelay, areAllRelaysLocal } from '../utils/helpers' import { markEventAsOfflineCreated } from './offlineSyncService' +import { getActiveRelayUrls } from './relayManager' /** * Unified write helper: add event to EventStore, detect connectivity, @@ -27,10 +27,13 @@ export async function publishEvent( const hasRemoteConnection = connectedRelays.some(url => !isLocalRelay(url)) + // Get active relay URLs from the pool + const activeRelays = getActiveRelayUrls(relayPool) + // Determine which relays we expect to succeed const expectedSuccessRelays = hasRemoteConnection - ? RELAYS - : RELAYS.filter(isLocalRelay) + ? activeRelays + : activeRelays.filter(isLocalRelay) const isLocalOnly = areAllRelaysLocal(expectedSuccessRelays) @@ -42,7 +45,7 @@ export async function publishEvent( } // Publish to all configured relays in the background (non-blocking) - relayPool.publish(RELAYS, event) + relayPool.publish(activeRelays, event) .then(() => { }) .catch((error) => {