From 0dd6be6d034570b1fc03e3b3e10fd66ab22ccf7e Mon Sep 17 00:00:00 2001 From: Silberengel Date: Fri, 10 Apr 2026 15:01:13 +0200 Subject: [PATCH] fix live-stream embedding --- src/PageManager.tsx | 2 ++ src/components/Embedded/EmbeddedNote.tsx | 19 ++++++++++-- src/components/Image/index.tsx | 2 +- src/components/ui/avatar.tsx | 8 +++-- src/lib/relay-list-builder.ts | 12 ++++++++ src/services/client-events.service.ts | 38 +++++++++++++++++++++--- src/types/react-video-fetchpriority.d.ts | 6 +++- 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/PageManager.tsx b/src/PageManager.tsx index 126e3a42..4aab2a06 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -423,6 +423,7 @@ export function useSmartNoteNavigation() { // If event is provided, store it in navigation event store to avoid re-fetching if (event) { + navigationEventStore.clear() navigationEventStore.setEvent(event) client.addEventToCache(event) } @@ -487,6 +488,7 @@ export function useSmartNoteNavigationOptional() { } const { noteId } = parsed if (event) { + navigationEventStore.clear() navigationEventStore.setEvent(event) client.addEventToCache(event) } diff --git a/src/components/Embedded/EmbeddedNote.tsx b/src/components/Embedded/EmbeddedNote.tsx index 1edc848a..f28b2b59 100644 --- a/src/components/Embedded/EmbeddedNote.tsx +++ b/src/components/Embedded/EmbeddedNote.tsx @@ -1,11 +1,13 @@ import { Skeleton } from '@/components/ui/skeleton' import { FAST_READ_RELAY_URLS, SEARCHABLE_RELAY_URLS, ExtendedKind } from '@/constants' +import { getFavoritesFeedRelayUrls } from '@/lib/favorites-feed-relays' import { isRenderableNoteKind } from '@/lib/note-renderable-kinds' import { useFetchEvent } from '@/hooks' import { normalizeUrl } from '@/lib/url' import { cn } from '@/lib/utils' import client from '@/services/client.service' import indexedDb from '@/services/indexed-db.service' +import { useFavoriteRelays } from '@/providers/favorite-relays-context' import { useTranslation } from 'react-i18next' import { useEffect, useMemo, useState } from 'react' import { Event, nip19 } from 'nostr-tools' @@ -311,6 +313,14 @@ function EmbeddedNoteNotFound({ containingEvent?: Event // Event that contains this embedded note - use its author's relays and relay hints }) { const { t } = useTranslation() + const { favoriteRelays, blockedRelays } = useFavoriteRelays() + const menuRelayUrls = useMemo( + () => + getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays) + .map((url) => normalizeUrl(url)) + .filter((url): url is string => Boolean(url)), + [favoriteRelays, blockedRelays] + ) const [isSearchingExternal, setIsSearchingExternal] = useState(false) const [triedExternal, setTriedExternal] = useState(false) const [externalRelays, setExternalRelays] = useState([]) @@ -392,7 +402,12 @@ function EmbeddedNoteNotFound({ .filter((url): url is string => Boolean(url)) const externalRelays = Array.from( - new Set([...normalizedHints, ...normalizedSearchableRelays, ...normalizedFastRead]) + new Set([ + ...normalizedHints, + ...menuRelayUrls, + ...normalizedSearchableRelays, + ...normalizedFastRead + ]) ) setExternalRelays(externalRelays) @@ -409,7 +424,7 @@ function EmbeddedNoteNotFound({ getExternalRelays() // containingEvent supplies e/a/q relay hints + author NIP-65 list — must rerun when parent loads - }, [noteId, containingEvent?.id]) + }, [noteId, containingEvent?.id, menuRelayUrls]) const handleTryExternalRelays = async () => { if (isSearchingExternal) return diff --git a/src/components/Image/index.tsx b/src/components/Image/index.tsx index b6ddb616..85d562a0 100644 --- a/src/components/Image/index.tsx +++ b/src/components/Image/index.tsx @@ -263,7 +263,7 @@ export default function Image({ decoding={effectiveHoldUntilClick ? 'async' : 'sync'} // `lazy` often never starts the request inside nested feed scrollers; always-load should fetch eagerly. loading="eager" - fetchPriority={fetchPriority} + {...(fetchPriority ? { fetchpriority: fetchPriority } : {})} draggable={false} onLoad={handleLoad} onError={handleError} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 8f63d0a4..54c5cb95 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -17,12 +17,16 @@ Avatar.displayName = AvatarPrimitive.Root.displayName const AvatarImage = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + /** Prefer banner LCP order; forwarded to `` as HTML `fetchpriority` (React warns on `fetchPriority`). */ + fetchPriority?: 'high' | 'low' | 'auto' + } +>(({ className, fetchPriority, ...props }, ref) => ( )) AvatarImage.displayName = AvatarPrimitive.Image.displayName diff --git a/src/lib/relay-list-builder.ts b/src/lib/relay-list-builder.ts index 1f728d09..16371dc8 100644 --- a/src/lib/relay-list-builder.ts +++ b/src/lib/relay-list-builder.ts @@ -234,6 +234,18 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio const localRelays = await getCacheRelayUrls(userPubkey) localRelays.forEach(addRelay) } + // Menu / feed “favorite relays” (kind 10012) — same list as the sidebar; not part of NIP-65 alone. + if (includeFavoriteRelays) { + try { + const favoriteRelays = await client.fetchFavoriteRelays(userPubkey) + favoriteRelays.forEach(addRelay) + logger.debug('[RelayListBuilder] Added user favorite relays (with inboxes path)', { + count: favoriteRelays.length + }) + } catch (error) { + logger.debug('[RelayListBuilder] Failed to fetch user favorite relays', { error }) + } + } } catch (error) { logger.debug('[RelayListBuilder] Failed to fetch user inboxes', { error }) } diff --git a/src/services/client-events.service.ts b/src/services/client-events.service.ts index 9dffe7cb..963c1e7f 100644 --- a/src/services/client-events.service.ts +++ b/src/services/client-events.service.ts @@ -31,8 +31,9 @@ import { buildComprehensiveRelayList } from '@/lib/relay-list-builder' import { normalizeUrl } from '@/lib/url' /** - * Build comprehensive relay list for event-by-id fetch: user's inboxes (+ cache), relay hints, - * author outboxes/inboxes when known, FAST_READ_RELAY_URLS, and SEARCHABLE_RELAY_URLS. + * Build comprehensive relay list for event-by-id fetch: user's inboxes (+ cache), **favorite relays + * (kind 10012, same as sidebar menu)**, relay hints, author outboxes/inboxes when known, + * FAST_READ_RELAY_URLS, and SEARCHABLE_RELAY_URLS. */ async function buildComprehensiveRelayListForEvents( authorPubkey: string | undefined, @@ -48,7 +49,8 @@ async function buildComprehensiveRelayListForEvents( containingEventRelays, includeFastReadRelays: true, includeSearchableRelays: true, - includeLocalRelays: true + includeLocalRelays: true, + includeFavoriteRelays: Boolean(client.pubkey) }) } @@ -109,6 +111,27 @@ export class EventService { return e } + /** + * Session cache is keyed by event `id` (hex). `fetchEvent("naddr1…")` has no hex until a REQ returns; + * scan for a replaceable whose `kind`/`pubkey`/`d` matches the naddr (e.g. live 30311 already loaded from ticker/embed). + */ + private getSessionEventIfMatchingNaddr(data: { + pubkey: string + kind: number + identifier: string + }): NEvent | undefined { + const pk = data.pubkey.toLowerCase() + const { kind, identifier } = data + for (const [, ev] of this.sessionEventCache.entries()) { + if (shouldDropEventOnIngest(ev)) continue + if (!isReplaceableEvent(ev.kind)) continue + if (ev.kind !== kind || ev.pubkey.toLowerCase() !== pk) continue + const d = ev.tags.find((t) => t[0] === 'd')?.[1] ?? '' + if (d === identifier) return ev + } + return undefined + } + private notifySessionEventWaiters(hexId: string): void { const waiters = this.sessionEventWaiters.get(hexId) if (!waiters?.size) return @@ -175,8 +198,15 @@ export class EventService { case 'nevent': hexId = data.id break - case 'naddr': + case 'naddr': { + const fromSession = this.getSessionEventIfMatchingNaddr({ + pubkey: data.pubkey, + kind: data.kind, + identifier: data.identifier + }) + if (fromSession) return fromSession break + } } } catch { return undefined diff --git a/src/types/react-video-fetchpriority.d.ts b/src/types/react-video-fetchpriority.d.ts index 98d98ef7..404828e3 100644 --- a/src/types/react-video-fetchpriority.d.ts +++ b/src/types/react-video-fetchpriority.d.ts @@ -1,8 +1,12 @@ -/** Chromium supports `fetchpriority` on `