diff --git a/package.json b/package.json index 04aa2b6d..8db40305 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "imwald", - "version": "23.10.3", + "version": "23.11.1", "description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery", "private": true, "type": "module", diff --git a/src/components/ProfileListBySearch/index.tsx b/src/components/ProfileListBySearch/index.tsx index 61730923..f6c1f6f0 100644 --- a/src/components/ProfileListBySearch/index.tsx +++ b/src/components/ProfileListBySearch/index.tsx @@ -1,5 +1,5 @@ import { useSecondaryPage } from '@/PageManager' -import { PROFILE_FETCH_RELAY_URLS } from '@/constants' +import { PROFILE_RELAY_URLS } from '@/constants' import { decodeProfileSearchQueryToPubkeyHex } from '@/lib/profile-search-query' import { buildAlexandriaEventsSearchUrlForTSearchParams } from '@/lib/alexandria-events-search-url' import { toProfile } from '@/lib/link' @@ -15,7 +15,7 @@ import { AlexandriaEventsSearchEmptyCta } from '@/components/AlexandriaEventsSea const LIMIT = 50 const PROFILE_SEARCH_RELAY_URLS = Array.from( - new Set(PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)) + new Set(PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)) ) export function ProfileListBySearch({ search }: { search: string }) { diff --git a/src/components/SearchResult/FullTextSearchByRelay.tsx b/src/components/SearchResult/FullTextSearchByRelay.tsx index 79c66e63..23c4a813 100644 --- a/src/components/SearchResult/FullTextSearchByRelay.tsx +++ b/src/components/SearchResult/FullTextSearchByRelay.tsx @@ -266,8 +266,10 @@ export default function FullTextSearchByRelay({ relayRows.length > 0 && relayRows.every((r) => r.phase === 'done' || r.phase === 'error') useEffect(() => { - const abort = new AbortController() + /** Unmount / total wall only — must not abort in-flight NIP-50 when the “first hits + …ms” scheduling cutoff runs. */ + const runAbort = new AbortController() let masterTimer: ReturnType | null = null + let stopSchedulingTimer: ReturnType | null = null const myRun = ++runGeneration.current const cleanupInvalidatePreviousRun = () => { runGeneration.current += 1 @@ -277,7 +279,11 @@ export default function FullTextSearchByRelay({ clearTimeout(masterTimer) masterTimer = null } - abort.abort() + if (stopSchedulingTimer != null) { + clearTimeout(stopSchedulingTimer) + stopSchedulingTimer = null + } + runAbort.abort() cleanupInvalidatePreviousRun() } @@ -307,38 +313,44 @@ export default function FullTextSearchByRelay({ /** Set when the first {@link runOneRelay} begins (first real NIP-50 query); master wall clock starts then. */ let waveT0: number | null = null - let waveEndAt = 0 - /** Only after ≥1 event from a relay: apply "first results + …ms" (empty EOSE must not shorten the wave). */ - let appliedRelativeWaveCutoff = false + /** After first preview-visible relay hits: stop dequeuing new relays; in-flight REQs keep their per-relay budget. */ + let stopSchedulingNewRelays = false + /** Only after ≥1 preview-visible event from a relay: stop starting new relays after …ms (empty EOSE must not shorten). */ + let appliedRelativeSchedulingCutoff = false - const scheduleMasterAbort = () => { + const scheduleMasterWallAbort = () => { if (masterTimer != null) { clearTimeout(masterTimer) masterTimer = null } - const ms = Math.max(0, waveEndAt - Date.now()) + if (waveT0 === null) return + const ms = Math.max(0, waveT0 + SEARCH_TOTAL_WALL_MS - Date.now()) masterTimer = setTimeout(() => { masterTimer = null - abort.abort() + stopSchedulingNewRelays = true + runAbort.abort() }, ms) } const beginWaveIfNeeded = () => { if (waveT0 !== null) return waveT0 = Date.now() - waveEndAt = waveT0 + SEARCH_TOTAL_WALL_MS - scheduleMasterAbort() + scheduleMasterWallAbort() } - const onFirstSearchHits = () => { - if (appliedRelativeWaveCutoff || waveT0 === null) return - appliedRelativeWaveCutoff = true - const now = Date.now() - waveEndAt = Math.min(waveT0 + SEARCH_TOTAL_WALL_MS, now + SEARCH_AFTER_FIRST_RELAY_MS) - scheduleMasterAbort() + const onFirstPreviewVisibleRelayHits = () => { + if (appliedRelativeSchedulingCutoff || waveT0 === null) return + appliedRelativeSchedulingCutoff = true + if (stopSchedulingTimer != null) { + clearTimeout(stopSchedulingTimer) + } + stopSchedulingTimer = setTimeout(() => { + stopSchedulingTimer = null + stopSchedulingNewRelays = true + }, SEARCH_AFTER_FIRST_RELAY_MS) } - abort.signal.addEventListener( + runAbort.signal.addEventListener( 'abort', () => { setRelayRows((prev) => @@ -415,7 +427,7 @@ export default function FullTextSearchByRelay({ includeOtherStoresFullText: true, fullTextStoreHitCap: 260 }) - if (myRun !== runGeneration.current || abort.signal.aborted) return + if (myRun !== runGeneration.current || runAbort.signal.aborted) return const mergedLocalMatching = mergedLocal.filter((e) => mergedSearchNoteHasPreviewBody(e)) if (mergedLocalMatching.length === 0) return applyMergedUpdate((map) => { @@ -432,25 +444,24 @@ export default function FullTextSearchByRelay({ })() const runOneRelay = async (relayUrl: string) => { - if (myRun !== runGeneration.current || abort.signal.aborted) return + if (myRun !== runGeneration.current || runAbort.signal.aborted) return beginWaveIfNeeded() const t0 = performance.now() - const remainingWaveMs = Math.max(500, waveEndAt - Date.now()) - const perRelayBudget = Math.min(SEARCH_PER_RELAY_QUERY_MS, remainingWaveMs) try { const { events: raw, connectionError } = await client.fetchEventsFromSingleRelay( relayUrl, filter, - { globalTimeout: perRelayBudget, signal: abort.signal } + { globalTimeout: SEARCH_PER_RELAY_QUERY_MS, signal: runAbort.signal } ) if (myRun !== runGeneration.current) return const sorted = [...raw] .sort((a, b) => compareEventsForDTagQuery(q, a, b)) .slice(0, FULL_TEXT_SEARCH_MAX_NOTES_PER_RELAY) + const previewVisible = sorted.filter((e) => mergedSearchNoteHasPreviewBody(e)) const ms = Math.round(performance.now() - t0) - if (sorted.length === 0 && connectionError) { + if (previewVisible.length === 0 && connectionError) { setRelayRows((prev) => prev.map((r) => r.relayUrl === relayUrl @@ -462,10 +473,10 @@ export default function FullTextSearchByRelay({ } mergeIntoHits(relayUrl, sorted) - void addSearchEventsToSessionCacheBatched(sorted, runGeneration, myRun) + void addSearchEventsToSessionCacheBatched(previewVisible, runGeneration, myRun) - if (sorted.length > 0) { - onFirstSearchHits() + if (previewVisible.length > 0) { + onFirstPreviewVisibleRelayHits() } setRelayRows((prev) => prev.map((r) => @@ -473,16 +484,16 @@ export default function FullTextSearchByRelay({ ? { ...r, phase: 'done', - eventCount: sorted.length, + eventCount: previewVisible.length, ms, - errorMessage: sorted.length > 0 ? undefined : connectionError + errorMessage: previewVisible.length > 0 ? undefined : connectionError } : r ) ) } catch (err) { if (myRun !== runGeneration.current) return - if (abort.signal.aborted) return + if (runAbort.signal.aborted) return const msg = err instanceof Error ? err.message : String(err) const ms = Math.round(performance.now() - t0) setRelayRows((prev) => @@ -494,7 +505,7 @@ export default function FullTextSearchByRelay({ } const worker = async () => { - while (myRun === runGeneration.current && !abort.signal.aborted) { + while (myRun === runGeneration.current && !runAbort.signal.aborted && !stopSchedulingNewRelays) { const relayUrl = nextRelayUrl() if (!relayUrl) break await runOneRelay(relayUrl) diff --git a/src/constants.ts b/src/constants.ts index f7736258..7fa8e8b0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -73,9 +73,7 @@ export const DESKTOP_APP_DOWNLOAD_URL_DEFAULT = export const DEFAULT_FAVORITE_RELAYS = [ 'wss://theforest.nostr1.com', - 'wss://christpill.nostr1.com', - 'wss://nostr.land', - 'wss://nostr21.com' + 'wss://nostr.land' ] /** @@ -284,7 +282,6 @@ export const PROFILE_FETCH_PROMISE_TIMEOUT_MS = 22_000 * @see https://blossom.happytavern.co/ — Lotus-style ephemeral Blossom (0x0 backend). */ export const STANDARD_BLOSSOM_UPLOAD_HOSTS = [ - { url: 'https://blossom.happytavern.co', labelKey: 'BlossomUploadOptionHappyTavern' }, { url: 'https://0x0.happytavern.co', labelKey: 'BlossomUploadOptionHappyTavern' }, { url: 'https://blossom.band', labelKey: 'BlossomUploadOptionBand' }, { url: 'https://blossom.primal.net', labelKey: 'BlossomUploadOptionPrimal' }, @@ -415,7 +412,8 @@ export const READ_ONLY_RELAY_URLS = [ 'wss://search.nos.today', 'wss://trending.nostr.wine', 'wss://relay.nip46.com', - 'wss://filter.nostr.wine' + 'wss://filter.nostr.wine', + 'wss://primus.nostr1.com' ] /** @@ -455,14 +453,10 @@ export const E_TAG_FILTER_BLOCKED_RELAY_URLS = [ // Optimized relay list for read operations (includes aggregator) export const FAST_READ_RELAY_URLS = [ 'wss://theforest.nostr1.com', - 'wss://orly-relay.imwald.eu', - 'wss://nostr.wine', 'wss://nostr.land', - 'wss://nostr21.com', - 'wss://thecitadel.nostr1.com', - 'wss://aggr.nostr.land', - 'wss://primus.nostr1.com', - 'wss://wheat.happytavern.co' + 'wss://nostr.wine', + 'wss://orly-relay.imwald.eu', + 'wss://nostr21.com' ] // Optimized relay list for write operations (no aggregator since it's read-only) @@ -477,11 +471,11 @@ export const FAST_WRITE_RELAY_URLS = [ /** Relays used for NIP-94 file metadata (kind 1063) / GIF discovery and publish. * Publish to all of these so GIFs are discoverable across clients; some may be temporarily down. */ export const GIF_RELAY_URLS = [ - 'wss://relay.gifbuddy.lol', 'wss://relay.damus.io', 'wss://relay.primal.net', 'wss://thecitadel.nostr1.com', 'wss://nos.lol', + 'wss://nostr.mom' ] export const SEARCHABLE_RELAY_URLS = [ @@ -489,29 +483,20 @@ export const SEARCHABLE_RELAY_URLS = [ 'wss://nostr.wine', 'wss://orly-relay.imwald.eu', 'wss://relay.noswhere.com', - 'wss://relay.wikifreedia.xyz', - 'wss://nostr.einundzwanzig.space', - 'wss://nostr-pub.wellorder.net', - 'wss://pyramid.fiatjaf.com/', - 'wss://nostrelites.org', - 'wss://wheat.happytavern.co' + 'wss://nostr-pub.wellorder.net' ] export const PROFILE_RELAY_URLS = [ 'wss://profiles.nostr1.com', 'wss://purplepag.es', - 'wss://relay.primal.net', - 'wss://relay.damus.io', - 'wss://nos.lol' + 'wss://profiles.nostrver.se/', + 'wss://indexer.coracle.social/' ] export const FOLLOWS_HISTORY_RELAY_URLS = [ 'wss://hist.nostr.land' ] -// Profile reads + NIP-50 profile search: search/index relays first, then fast read + profile mirrors (order preserved; dedupe at use sites). -export const PROFILE_FETCH_RELAY_URLS = [...SEARCHABLE_RELAY_URLS, ...FAST_READ_RELAY_URLS, ...PROFILE_RELAY_URLS] - export const ExtendedKind = { PICTURE: 20, VIDEO: 21, diff --git a/src/hooks/useSearchProfiles.tsx b/src/hooks/useSearchProfiles.tsx index b5bde499..12422afb 100644 --- a/src/hooks/useSearchProfiles.tsx +++ b/src/hooks/useSearchProfiles.tsx @@ -1,11 +1,11 @@ -import { PROFILE_FETCH_RELAY_URLS } from '@/constants' +import { PROFILE_RELAY_URLS } from '@/constants' import { normalizeUrl } from '@/lib/url' import client from '@/services/client.service' import { TProfile } from '@/types' import { useEffect, useState } from 'react' const PROFILE_SEARCH_RELAY_URLS = Array.from( - new Set(PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)) + new Set(PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)) ) export function useSearchProfiles(search: string, limit: number) { diff --git a/src/lib/favorites-feed-relays.ts b/src/lib/favorites-feed-relays.ts index 957f3459..0fec7f10 100644 --- a/src/lib/favorites-feed-relays.ts +++ b/src/lib/favorites-feed-relays.ts @@ -2,7 +2,7 @@ import { DEFAULT_FAVORITE_RELAYS, DOCUMENT_RELAY_URLS, FAST_READ_RELAY_URLS, - PROFILE_FETCH_RELAY_URLS, + PROFILE_RELAY_URLS, READ_ONLY_RELAY_URLS, isDocumentRelayKind, relayFilterIncludesSocialKindBlockedKind @@ -222,7 +222,7 @@ export function buildProfilePageReadRelayUrls( ) /** Authors without kind 10002: widen REQ targets so notes/metadata are still discoverable on index relays. */ if (authorHasNoNip65) { - const profileSource = useGlobal ? PROFILE_FETCH_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer() + const profileSource = useGlobal ? PROFILE_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer() const profileFetchLayer = profileSource.map((u) => normalizeUrl(u) || u).filter(Boolean) as string[] return mergeRelayUrlLayers([urls, profileFetchLayer], blockedRelays).slice(0, maxRelays + 8) } diff --git a/src/lib/relay-list-builder.ts b/src/lib/relay-list-builder.ts index 20ab72a9..734f6126 100644 --- a/src/lib/relay-list-builder.ts +++ b/src/lib/relay-list-builder.ts @@ -9,7 +9,7 @@ * - Includes seen relays */ -import { FAST_READ_RELAY_URLS, PROFILE_FETCH_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' +import { FAST_READ_RELAY_URLS, PROFILE_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' import { feedRelayPolicyUrls } from '@/features/feed/relay-policy' import { mergeRelayUrlLayers, userReadRelaysWithHttp } from '@/lib/favorites-feed-relays' import { urlIsNonLocalForRemoteViewer } from '@/lib/relay-list-sanitize' @@ -38,7 +38,7 @@ function dedupeNormalizedRelayUrls(urls: string[]): string[] { * PROFILE_FETCH + FAST_READ. */ function exploreDiscoveryBootstrapRelayUrls(): string[] { - return dedupeNormalizedRelayUrls([...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS]) + return dedupeNormalizedRelayUrls([...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS]) } export interface RelayListBuilderOptions { @@ -54,7 +54,7 @@ export interface RelayListBuilderOptions { containingEventRelays?: string[] /** Whether to include user's own relays (read/write/local) - for profiles/metadata */ includeUserOwnRelays?: boolean - /** Whether to include PROFILE_FETCH_RELAY_URLS - for profiles/metadata */ + /** Whether to include PROFILE_RELAY_URLS - for profiles/metadata */ includeProfileFetchRelays?: boolean /** Whether to include FAST_READ_RELAY_URLS as fallback */ includeFastReadRelays?: boolean @@ -72,7 +72,7 @@ export interface RelayListBuilderOptions { /** Whether to include user's favorite relays (kind 10012) */ includeFavoriteRelays?: boolean /** - * When true with fast-read / searchable / profile-fetch includes: insert `PROFILE_FETCH_RELAY_URLS`, + * When true with fast-read / searchable / profile-fetch includes: insert `PROFILE_RELAY_URLS`, * `FAST_READ_RELAY_URLS`, and `SEARCHABLE_RELAY_URLS` immediately after hints/seen/containing and **before** * author + user NIP-65 lists. Used for batched metadata and embed fetches so public mirrors are not queued * behind broken personal relays under the global connection cap. @@ -133,7 +133,7 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio // connection slots on broken personal relays before PROFILE_FETCH + FAST_READ answer). if (preferPublicReadRelaysEarly) { if (includeProfileFetchRelays) { - PROFILE_FETCH_RELAY_URLS.forEach(addRelay) + PROFILE_RELAY_URLS.forEach(addRelay) } if (includeFastReadRelays) { FAST_READ_RELAY_URLS.forEach(addRelay) @@ -212,7 +212,7 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio // 6. Profile fetch relays (for profiles/metadata) if (includeProfileFetchRelays) { - PROFILE_FETCH_RELAY_URLS.forEach(addRelay) + PROFILE_RELAY_URLS.forEach(addRelay) } // 7. Fast read relays (fallback) diff --git a/src/lib/tombstone-events.ts b/src/lib/tombstone-events.ts index 6684506c..896f09e0 100644 --- a/src/lib/tombstone-events.ts +++ b/src/lib/tombstone-events.ts @@ -1,4 +1,4 @@ -import { PROFILE_FETCH_RELAY_URLS } from '@/constants' +import { PROFILE_RELAY_URLS } from '@/constants' import { normalizeAnyRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url' import type { TRelayList } from '@/types' @@ -16,7 +16,7 @@ export function buildDeletionRelayUrls(relayList: TRelayList | null | undefined) const httpW = relayList?.httpWrite ?? [] if (!relayList?.read?.length && !relayList?.write?.length && !httpR.length && !httpW.length) { return Array.from( - new Set(PROFILE_FETCH_RELAY_URLS.map((url) => normalizeUrl(url) || url).filter(Boolean)) + new Set(PROFILE_RELAY_URLS.map((url) => normalizeUrl(url) || url).filter(Boolean)) ).slice(0, 20) } const ws = relayList?.write ?? [] @@ -27,7 +27,7 @@ export function buildDeletionRelayUrls(relayList: TRelayList | null | undefined) ...rs.slice(0, 8).map((url: string) => normalizeUrl(url) || url), ...httpW.map((url: string) => normalizeHttpRelayUrl(url) || url), ...httpR.slice(0, 8).map((url: string) => normalizeHttpRelayUrl(url) || url), - ...PROFILE_FETCH_RELAY_URLS.map((url: string) => normalizeAnyRelayUrl(url) || url) + ...PROFILE_RELAY_URLS.map((url: string) => normalizeAnyRelayUrl(url) || url) ]) ).slice(0, 20) } diff --git a/src/lib/viewer-relay-defaults.ts b/src/lib/viewer-relay-defaults.ts index d3c84786..6e64433d 100644 --- a/src/lib/viewer-relay-defaults.ts +++ b/src/lib/viewer-relay-defaults.ts @@ -1,7 +1,7 @@ import { DEFAULT_FAVORITE_RELAYS, FAST_READ_RELAY_URLS, - PROFILE_FETCH_RELAY_URLS + PROFILE_RELAY_URLS } from '@/constants' import { normalizeUrl } from '@/lib/url' @@ -43,7 +43,7 @@ const fastReadKeySet = (): Set => { /** PROFILE_FETCH stack with {@link FAST_READ_RELAY_URLS} entries removed (order preserved). */ export function profileFetchRelayUrlsWithoutFastReadLayer(): string[] { const drop = fastReadKeySet() - return PROFILE_FETCH_RELAY_URLS.filter((u) => { + return PROFILE_RELAY_URLS.filter((u) => { const n = (normalizeUrl(u) || u).toLowerCase() return n && !drop.has(n) }) diff --git a/src/pages/primary/SpellsPage/index.tsx b/src/pages/primary/SpellsPage/index.tsx index 0553d31e..bd4926ac 100644 --- a/src/pages/primary/SpellsPage/index.tsx +++ b/src/pages/primary/SpellsPage/index.tsx @@ -27,7 +27,7 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider' import { useBookmarks } from '@/providers/bookmarks-context' import { useNostr } from '@/providers/NostrProvider' -import { useNotificationThreadWatch } from '@/providers/NotificationThreadWatchProvider' +import { useNotificationThreadWatchOptional } from '@/providers/NotificationThreadWatchProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useUserTrust } from '@/contexts/user-trust-context' import { dedupeFollowSetEventsByD } from '@/lib/follow-set-spell' @@ -89,7 +89,9 @@ const SpellsPage = forwardRef(function SpellsPage( } = useNostr() const { addBookmark, removeBookmark } = useBookmarks() const { hideUntrustedNotifications } = useUserTrust() - const { eventsIFollowListEvent, eventsIMutedListEvent } = useNotificationThreadWatch() + const notificationThreadWatch = useNotificationThreadWatchOptional() + const eventsIFollowListEvent = notificationThreadWatch?.eventsIFollowListEvent ?? null + const eventsIMutedListEvent = notificationThreadWatch?.eventsIMutedListEvent ?? null const { isSmallScreen } = useScreenSize() const { favoriteRelays, blockedRelays } = useFavoriteRelays() const useGlobalRelayBootstrap = useGlobalRelayBootstrapDefaults() diff --git a/src/pages/secondary/BookmarkListPage/index.tsx b/src/pages/secondary/BookmarkListPage/index.tsx index 14fa0726..ca16cf97 100644 --- a/src/pages/secondary/BookmarkListPage/index.tsx +++ b/src/pages/secondary/BookmarkListPage/index.tsx @@ -27,7 +27,7 @@ import { getLatestEvent } from '@/lib/event' import { buildAccountListRelayUrlsForMerge } from '@/lib/account-list-relay-urls' import { fetchLatestReplaceableListEvent } from '@/lib/replaceable-list-latest' import { normalizeUrl } from '@/lib/url' -import { PROFILE_FETCH_RELAY_URLS } from '@/constants' +import { PROFILE_RELAY_URLS } from '@/constants' import { queryService } from '@/services/client.service' import dayjs from 'dayjs' import { Code, Eraser, MoreVertical } from 'lucide-react' @@ -64,7 +64,7 @@ const BookmarkListPage = forwardRef( const urls = Array.from( new Set( [ - ...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u), + ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...(relayList?.write ?? []).map((u) => normalizeUrl(u) || u) ].filter(Boolean) ) diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 00867988..21699e62 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -7,7 +7,6 @@ import { DEFAULT_FAVORITE_RELAYS, FAST_READ_RELAY_URLS, ExtendedKind, - PROFILE_FETCH_RELAY_URLS, PROFILE_RELAY_URLS, SEARCHABLE_RELAY_URLS, UNSIGNED_EXPERIMENTAL_KIND_MAX, @@ -543,7 +542,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { ...mergedRelayList.write.map((url: string) => normalizeUrl(url) || url), ...mergedRelayList.read.map((url: string) => normalizeUrl(url) || url), ...FAST_READ_RELAY_URLS.map((url: string) => normalizeUrl(url) || url), - ...PROFILE_FETCH_RELAY_URLS.map((url: string) => normalizeUrl(url) || url) + ...PROFILE_RELAY_URLS.map((url: string) => normalizeUrl(url) || url) ] const fetchRelays = Array.from(new Set(normalizedRelays)).slice(0, 16) const events = await queryService.fetchEvents(fetchRelays, [ @@ -706,7 +705,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { ...mergedRelayList.write.map((u) => normalizeUrl(u) || u), ...mergedRelayList.read.map((u) => normalizeUrl(u) || u), ...SEARCHABLE_RELAY_URLS.map((u) => normalizeUrl(u) || u), - ...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u), + ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u) ]) ).filter(Boolean) @@ -914,7 +913,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { ...rl.write.map((u) => normalizeUrl(u) || u), ...rl.read.map((u) => normalizeUrl(u) || u), ...SEARCHABLE_RELAY_URLS.map((u) => normalizeUrl(u) || u), - ...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u), + ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u) ]) ).filter(Boolean) diff --git a/src/services/client-replaceable-events.service.ts b/src/services/client-replaceable-events.service.ts index 6367817d..1a5e7e10 100644 --- a/src/services/client-replaceable-events.service.ts +++ b/src/services/client-replaceable-events.service.ts @@ -7,7 +7,7 @@ import { METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS, METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS, PROFILE_BATCH_NETWORK_LOAD_TIMEOUT_MS, - PROFILE_FETCH_RELAY_URLS, + PROFILE_RELAY_URLS, READ_ONLY_RELAY_URLS, RECOMMENDED_BLOSSOM_SERVERS } from '@/constants' @@ -120,7 +120,7 @@ export class ReplaceableEventService { /** * Build comprehensive relay list: author's outboxes + user's inboxes + relay hints + defaults - * For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_FETCH_RELAY_URLS + * For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_RELAY_URLS */ private async buildComprehensiveRelayListForAuthor( authorPubkey: string, @@ -138,7 +138,7 @@ export class ReplaceableEventService { relayHints, containingEventRelays, includeUserOwnRelays: isProfileOrMetadata, // For profiles/metadata, include user's own relays - includeProfileFetchRelays: isProfileOrMetadata, // For profiles/metadata, include PROFILE_FETCH_RELAY_URLS + includeProfileFetchRelays: isProfileOrMetadata, // For profiles/metadata, include PROFILE_RELAY_URLS includeFastReadRelays: true, includeLocalRelays: true }) @@ -148,7 +148,7 @@ export class ReplaceableEventService { * Fetch replaceable event (profile, relay list, etc.) * Uses DataLoader to batch IndexedDB checks and network fetches * ALWAYS uses: author's outboxes + user's inboxes + relay hints + defaults - * For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_FETCH_RELAY_URLS + * For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_RELAY_URLS * * @param pubkey - Author's pubkey * @param kind - Event kind @@ -518,11 +518,11 @@ export class ReplaceableEventService { Array.from(missingGroups.entries()).map(async ([kind, missingItems]) => { const pubkeys = missingItems.map(item => item.pubkey) // ALWAYS use comprehensive relay list: author's outboxes + user's inboxes + defaults - // For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_FETCH_RELAY_URLS + // For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_RELAY_URLS // For each pubkey, build comprehensive relay list // CRITICAL FIX: For batch fetches, use default relays instead of fetching relay lists for each author // Fetching relay lists for hundreds of authors causes infinite loops and browser crashes - // Use PROFILE_FETCH_RELAY_URLS + FAST_READ_RELAY_URLS for profiles, or FAST_READ_RELAY_URLS for other kinds. + // Use PROFILE_RELAY_URLS + FAST_READ_RELAY_URLS for profiles, or FAST_READ_RELAY_URLS for other kinds. // For metadata with a logged-in user, merge defaults with {@link buildComprehensiveRelayList}: inboxes (read), // local/cache relays (10432), favorite relays (10012), plus profile + fast read — same idea as favorites feed // / inbox-scoped discovery without per-author relay list fetches. @@ -546,10 +546,10 @@ export class ReplaceableEventService { preferPublicReadRelaysEarly: true }) } catch { - relayUrls = Array.from(new Set([...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS])) + relayUrls = Array.from(new Set([...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS])) } } else { - relayUrls = Array.from(new Set([...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS])) + relayUrls = Array.from(new Set([...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS])) } } else if (kind === ExtendedKind.FAVORITE_RELAYS) { relayUrls = await buildExploreProfileAndUserRelayList(client.pubkey) @@ -558,7 +558,7 @@ export class ReplaceableEventService { // and 100ms EOSE loses the race when several relays are down. relayUrls = Array.from( new Set( - [...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( + [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( (u) => normalizeUrl(u) || u ) ) @@ -567,7 +567,7 @@ export class ReplaceableEventService { // Contacts (kind 3): aggregators + profile mirrors + fast read. relayUrls = Array.from( new Set( - [...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( + [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( (u) => normalizeUrl(u) || u ) ) @@ -576,7 +576,7 @@ export class ReplaceableEventService { // NIP-65 (10002): aggregators + profile mirrors + fast read. relayUrls = Array.from( new Set( - [...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( + [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( (u) => normalizeUrl(u) || u ) ) @@ -585,7 +585,7 @@ export class ReplaceableEventService { // Mute / bookmark lists: same distribution as contacts; FAST_READ + mirrors. relayUrls = Array.from( new Set( - [...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( + [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( (u) => normalizeUrl(u) || u ) ) @@ -594,7 +594,7 @@ export class ReplaceableEventService { // NIP-A3 kind 10133: aggregators + profile mirrors + fast read. relayUrls = Array.from( new Set( - [...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( + [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( (u) => normalizeUrl(u) || u ) ) @@ -937,7 +937,7 @@ export class ReplaceableEventService { new Set([ ...relayHints, ...authorRelays, - ...PROFILE_FETCH_RELAY_URLS, + ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS ]) ) diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 402bac74..79795465 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -29,7 +29,7 @@ import { EARLY_PUBLISH_SUCCESS_GRACE_MS, DEFAULT_FAVORITE_RELAYS, NIP66_DISCOVERY_RELAY_URLS, - PROFILE_FETCH_RELAY_URLS, + PROFILE_RELAY_URLS, PROFILE_RELAY_URLS, READ_ONLY_RELAY_URLS, NIP42_POOL_AUTOMATIC_AUTH_RELAY_URLS, @@ -1242,12 +1242,12 @@ class ClientService extends EventTarget { ].includes(event.kind) ) { bootstrapExtras.push( - ...(useGlobalRelayDefaults ? PROFILE_FETCH_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer()) + ...(useGlobalRelayDefaults ? PROFILE_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer()) ) - logger.debug('[DetermineTargetRelays] Relay list event detected, adding PROFILE_FETCH_RELAY_URLS', { + logger.debug('[DetermineTargetRelays] Relay list event detected, adding PROFILE_RELAY_URLS', { kind: event.kind, profileFetchRelays: useGlobalRelayDefaults - ? PROFILE_FETCH_RELAY_URLS + ? PROFILE_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer(), additionalRelayCount: bootstrapExtras.length }) @@ -1264,7 +1264,7 @@ class ClientService extends EventTarget { }) } else if (event.kind === ExtendedKind.RSS_FEED_LIST) { if (useGlobalRelayDefaults) { - bootstrapExtras.push(...FAST_WRITE_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS) + bootstrapExtras.push(...FAST_WRITE_RELAY_URLS, ...PROFILE_RELAY_URLS) } else { bootstrapExtras.push(...profileFetchRelayUrlsWithoutFastReadLayer()) } @@ -3733,7 +3733,7 @@ class ClientService extends EventTarget { /** * Npubs for @-mention dropdown: (1) follow-list profiles matching the query, - * (2) local index, (3) kind-0 NIP-50 search on {@link PROFILE_FETCH_RELAY_URLS} (includes search relays + profile mirrors; deduped). + * (2) local index, (3) kind-0 NIP-50 search on {@link PROFILE_RELAY_URLS} (includes search relays + profile mirrors; deduped). * Returns cached results immediately, then streams relay results via callback. */ /** @@ -3775,7 +3775,7 @@ class ClientService extends EventTarget { const pk = authorPubkey?.trim().toLowerCase() if (!pk || !/^[0-9a-f]{64}$/.test(pk)) return - const urls = (relayUrls.length > 0 ? relayUrls : [...PROFILE_FETCH_RELAY_URLS]) + const urls = (relayUrls.length > 0 ? relayUrls : [...PROFILE_RELAY_URLS]) .map((u) => normalizeUrl(u) || u) .filter(Boolean) const capped = Array.from(new Set(urls)).slice(0, 16) @@ -3867,7 +3867,7 @@ class ClientService extends EventTarget { // Relay query starts immediately so it can run in parallel with local + follow work (slow relays). const profileSearchRelayUrls = dedupeNormalizeRelayUrlsOrdered( - PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean) + PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean) ) const relayTask = q.length >= 1 @@ -4152,8 +4152,8 @@ class ClientService extends EventTarget { const [fallback] = await this.mergeRelayListsFromStoredOnly([pubkey]) return fallback! } catch { - const read = PROFILE_FETCH_RELAY_URLS - const write = PROFILE_FETCH_RELAY_URLS + const read = PROFILE_RELAY_URLS + const write = PROFILE_RELAY_URLS return { write, read, @@ -4174,7 +4174,7 @@ class ClientService extends EventTarget { /** * Merge relay list from IndexedDB only (no network). Same rules as a timed-out {@link fetchRelayLists}: - * defaults to {@link PROFILE_FETCH_RELAY_URLS} when kind 10002 is missing. + * defaults to {@link PROFILE_RELAY_URLS} when kind 10002 is missing. */ async peekRelayListFromStorage(pubkey: string): Promise { const [rl] = await this.mergeRelayListsFromStoredOnly([pubkey]) @@ -4308,14 +4308,14 @@ class ClientService extends EventTarget { if (isOwnRelayList && storedCacheEvent) { const cacheRelayList = getRelayListFromEvent(storedCacheEvent) return mergeKind10243({ - write: cacheRelayList.write.length > 0 ? cacheRelayList.write : PROFILE_FETCH_RELAY_URLS, - read: cacheRelayList.read.length > 0 ? cacheRelayList.read : PROFILE_FETCH_RELAY_URLS, + write: cacheRelayList.write.length > 0 ? cacheRelayList.write : PROFILE_RELAY_URLS, + read: cacheRelayList.read.length > 0 ? cacheRelayList.read : PROFILE_RELAY_URLS, originalRelays: cacheRelayList.originalRelays, ...emptyHttp }) } - let read = PROFILE_FETCH_RELAY_URLS - let write = PROFILE_FETCH_RELAY_URLS + let read = PROFILE_RELAY_URLS + let write = PROFILE_RELAY_URLS if (!isOwnRelayList) { const stripped = stripMailboxLocalUrlsForRemoteViewers({ read, write }) read = @@ -4551,8 +4551,8 @@ class ClientService extends EventTarget { try { return await this.mergeRelayListsFromStoredOnly(pubkeys) } catch { - const read = PROFILE_FETCH_RELAY_URLS - const write = PROFILE_FETCH_RELAY_URLS + const read = PROFILE_RELAY_URLS + const write = PROFILE_RELAY_URLS return pubkeys.map(() => ({ write, read, @@ -4571,7 +4571,7 @@ class ClientService extends EventTarget { /** * Fetch cache relay events (kind 10432) from multiple sources: - * - PROFILE_FETCH_RELAY_URLS + * - PROFILE_RELAY_URLS * - User's inboxes (read relays from kind 10002) * - User's outboxes (write relays from kind 10002) */ @@ -4708,7 +4708,7 @@ class ClientService extends EventTarget { ...relayList.write.map((u) => normalizeUrl(u) || u), ...relayList.read.map((u) => normalizeUrl(u) || u), ...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u), - ...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u) + ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u) ]).filter(Boolean) const capped = urls.slice(0, 20) if (capped.length === 0) return []