Browse Source

fix live-stream embedding

imwald
Silberengel 3 weeks ago
parent
commit
0dd6be6d03
  1. 2
      src/PageManager.tsx
  2. 19
      src/components/Embedded/EmbeddedNote.tsx
  3. 2
      src/components/Image/index.tsx
  4. 8
      src/components/ui/avatar.tsx
  5. 12
      src/lib/relay-list-builder.ts
  6. 38
      src/services/client-events.service.ts
  7. 6
      src/types/react-video-fetchpriority.d.ts

2
src/PageManager.tsx

@ -423,6 +423,7 @@ export function useSmartNoteNavigation() { @@ -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() { @@ -487,6 +488,7 @@ export function useSmartNoteNavigationOptional() {
}
const { noteId } = parsed
if (event) {
navigationEventStore.clear()
navigationEventStore.setEvent(event)
client.addEventToCache(event)
}

19
src/components/Embedded/EmbeddedNote.tsx

@ -1,11 +1,13 @@ @@ -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({ @@ -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<string[]>([])
@ -392,7 +402,12 @@ function EmbeddedNoteNotFound({ @@ -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({ @@ -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

2
src/components/Image/index.tsx

@ -263,7 +263,7 @@ export default function Image({ @@ -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}

8
src/components/ui/avatar.tsx

@ -17,12 +17,16 @@ Avatar.displayName = AvatarPrimitive.Root.displayName @@ -17,12 +17,16 @@ Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> & {
/** Prefer banner LCP order; forwarded to `<img>` as HTML `fetchpriority` (React warns on `fetchPriority`). */
fetchPriority?: 'high' | 'low' | 'auto'
}
>(({ className, fetchPriority, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
{...(fetchPriority ? { fetchpriority: fetchPriority } : {})}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName

12
src/lib/relay-list-builder.ts

@ -234,6 +234,18 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio @@ -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 })
}

38
src/services/client-events.service.ts

@ -31,8 +31,9 @@ import { buildComprehensiveRelayList } from '@/lib/relay-list-builder' @@ -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( @@ -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 { @@ -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,9 +198,16 @@ export class EventService { @@ -175,9 +198,16 @@ 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
}

6
src/types/react-video-fetchpriority.d.ts vendored

@ -1,8 +1,12 @@ @@ -1,8 +1,12 @@
/** Chromium supports `fetchpriority` on `<video>`; @types/react may lag behind. */
/** Chromium supports `fetchpriority` on `<video>` / `<img>`; @types/react may lag behind. */
import 'react'
declare module 'react' {
interface VideoHTMLAttributes<T> {
fetchPriority?: 'high' | 'low' | 'auto'
}
/** HTML `fetchpriority`; use on `<img>` — React warns on camelCase `fetchPriority` for DOM imgs. */
interface ImgHTMLAttributes<T> {
fetchpriority?: 'high' | 'low' | 'auto'
}
}

Loading…
Cancel
Save