From 9302260cad89efdc0dc64b1ae2840087427e5aab Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 13 May 2026 09:27:15 +0200 Subject: [PATCH] bug-fixes --- src/components/ErrorBoundary.tsx | 1 + src/components/NoteList/index.tsx | 8 +++-- src/lib/wisp-trending-relay.ts | 6 ++-- src/services/client.service.ts | 53 +++++++++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 98352f56..0557357a 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -16,6 +16,7 @@ function isLikelyBrokenReactContextFromHmr(message: string): boolean { return ( /must be used within (a )?[\w]+/i.test(message) || message.includes('useNostr must be used within') || + message.includes('useContentPolicy must be used within') || message.includes('useInterestList must be used within') || (message.includes('useContext') && message.includes('null')) ) diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index 6bfdb824..65151d55 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -27,7 +27,7 @@ import { isLocalNetworkUrl, normalizeUrl } from '@/lib/url' import { eventPassesNoteListKindPicker } from '@/lib/feed-kind-filter' import { shouldIncludeZapReceiptAtReplyThreshold } from '@/lib/event-metadata' import { isTouchDevice } from '@/lib/utils' -import { useContentPolicy } from '@/providers/ContentPolicyProvider' +import { useContentPolicyOptional } from '@/providers/ContentPolicyProvider' import { useDeletedEvent } from '@/providers/DeletedEventProvider' import { useMuteList } from '@/contexts/mute-list-context' import { muteSetHas } from '@/lib/mute-set' @@ -827,7 +827,11 @@ const NoteList = forwardRef( const { startLogin, pubkey } = useNostr() const { isUserTrusted } = useUserTrust() const { mutePubkeySet } = useMuteList() - const { hideContentMentioningMutedUsers, isOffline } = useContentPolicy() + const contentPolicy = useContentPolicyOptional() + const hideContentMentioningMutedUsers = contentPolicy?.hideContentMentioningMutedUsers ?? false + const isOffline = + contentPolicy?.isOffline ?? + (!navigator.onLine || (navigator as Navigator & { connection?: { type?: string } }).connection?.type === 'none') const { isEventDeleted } = useDeletedEvent() const { zapReplyThreshold } = useZap() const { favoriteRelays, blockedRelays } = useFavoriteRelays() diff --git a/src/lib/wisp-trending-relay.ts b/src/lib/wisp-trending-relay.ts index 2623b448..a65fcc66 100644 --- a/src/lib/wisp-trending-relay.ts +++ b/src/lib/wisp-trending-relay.ts @@ -2,9 +2,9 @@ import { ExtendedKind } from '@/constants' import { normalizeUrl } from '@/lib/url' /** - * Trending notes stream from nostrarchives, consumed by - * {@link https://github.com/barrydeen/wisp | Wisp} (Android). Same URL shape as Wisp’s - * `buildTrendingRelayUrl` / `FEED_KINDS` REQ. + * Trending notes stream from nostrarchives (path-based relay URL). The WebSocket speaks **standard NIP-01** + * `REQ` / `EVENT` / `EOSE` — safe for nostr-tools {@link SimplePool}. Same URL shape as Wisp’s + * {@link https://github.com/barrydeen/wisp | Wisp} (Android) `buildTrendingRelayUrl` / `FEED_KINDS` REQ. */ export type WispTrendingMetric = 'reactions' | 'replies' | 'reposts' | 'zaps' diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 0aea1b85..9e4026c2 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -622,6 +622,50 @@ class ClientService extends EventTarget { }) } + /** + * When the user explicitly picked relays, reorder **only within that list** using a bounded + * {@link fetchRelayListWithPublishTimeout} for the author — not {@link fetchRelayLists} for every + * reply/mention context (which could stall publish for a long time on slow or failing relays). + */ + private async prioritizeSpecifiedRelayPickerUrls( + pickerUrls: string[], + event: NEvent, + favoriteRelayUrls: string[] = [] + ): Promise { + const deduped = dedupeNormalizeRelayUrlsOrdered(pickerUrls) + let relayList: TRelayList + try { + relayList = await this.fetchRelayListWithPublishTimeout(event.pubkey) + } catch { + relayList = this.emptyRelayListForPublish() + } + const writeSet = new Set() + for (const u of relayList.write ?? []) { + const n = normalizeUrl(u) || u + if (n) writeSet.add(n) + } + for (const u of relayList.httpWrite ?? []) { + const n = normalizeHttpRelayUrl(u) || u + if (n) writeSet.add(n) + } + const favSet = new Set( + favoriteRelayUrls.map((f) => normalizeUrl(f) || f).filter((u): u is string => !!u) + ) + + const outbox: string[] = [] + const favRest: string[] = [] + const other: string[] = [] + for (const u of deduped) { + const n = normalizeAnyRelayUrl(u) || u + if (!n) continue + if (writeSet.has(n)) outbox.push(u) + else if (favSet.has(n)) favRest.push(u) + else other.push(u) + } + const merged = dedupeNormalizeRelayUrlsOrdered([...outbox, ...favRest, ...other]) + return this.filterPublishingRelays(merged, event).slice(0, MAX_PUBLISH_RELAYS) + } + private async prioritizePublishUrlList( relayUrls: string[], event: NEvent, @@ -632,7 +676,7 @@ class ClientService extends EventTarget { const pubkeyOrder = Array.from(new Set([event.pubkey, ...ctx])) let lists: TRelayList[] = [] try { - lists = await this.fetchRelayLists(pubkeyOrder) + lists = await this.fetchRelayListsWithPublishTimeout(pubkeyOrder) } catch { lists = [] } @@ -1120,7 +1164,12 @@ class ClientService extends EventTarget { if (specifiedRelayUrls?.length) { const checkedCount = specifiedRelayUrls.length - relays = await this.prioritizePublishUrlListWithTimeout(relays, event, favoriteRelayUrls ?? []) + /** + * User already chose relays — do not block publish on {@link fetchRelayLists} for every reply/mention + * context pubkey (can take tens of seconds on bad networks). Reorder only within the picker set: + * author's NIP-65 write relays first, then favorites among the selection, then remaining picker order. + */ + relays = await this.prioritizeSpecifiedRelayPickerUrls(relays, event, favoriteRelayUrls ?? []) if (checkedCount > relays.length) { logger.info('[Publish] Relay picker: checked count exceeds per-publish cap (stage 1)', { checkedInRelayPicker: checkedCount,