Browse Source

bug-fix

imwald
Silberengel 4 weeks ago
parent
commit
e156cb5347
  1. 47
      src/components/Profile/index.tsx
  2. 12
      src/hooks/useProfileAuthorFeedSubRequests.ts
  3. 15
      src/providers/NostrProvider/index.tsx
  4. 8
      src/services/client-replaceable-events.service.ts

47
src/components/Profile/index.tsx

@ -125,7 +125,7 @@ export default function Profile({
const { profile, isFetching } = useFetchProfile(id) const { profile, isFetching } = useFetchProfile(id)
profilePubkeyRef.current = profile?.pubkey ?? null profilePubkeyRef.current = profile?.pubkey ?? null
const { pubkey: accountPubkey, publish, checkLogin } = useNostr() const { pubkey: accountPubkey, profileEvent: accountProfileEvent, publish, checkLogin } = useNostr()
const [paymentInfo, setPaymentInfo] = useState<ReturnType<typeof getPaymentInfoFromEvent> | null>(null) const [paymentInfo, setPaymentInfo] = useState<ReturnType<typeof getPaymentInfoFromEvent> | null>(null)
const [profileEvent, setProfileEvent] = useState<NostrEvent | undefined>(undefined) const [profileEvent, setProfileEvent] = useState<NostrEvent | undefined>(undefined)
const [openZapDialog, setOpenZapDialog] = useState(false) const [openZapDialog, setOpenZapDialog] = useState(false)
@ -140,9 +140,20 @@ export default function Profile({
const { relayUrls: currentBrowsingRelayUrls } = useCurrentRelays() const { relayUrls: currentBrowsingRelayUrls } = useCurrentRelays()
const { relaySets, favoriteRelays } = useFavoriteRelays() const { relaySets, favoriteRelays } = useFavoriteRelays()
const isSelf = accountPubkey === profile?.pubkey
const effectiveProfileEvent = useMemo(() => {
if (!isSelf || !accountProfileEvent) return profileEvent
if (!profileEvent) return accountProfileEvent
return accountProfileEvent.created_at >= profileEvent.created_at ? accountProfileEvent : profileEvent
}, [isSelf, profileEvent, accountProfileEvent])
const mergedPaymentMethods = useMemo( const mergedPaymentMethods = useMemo(
() => sortMergedPaymentMethods(mergePaymentMethods(paymentInfo, profile ?? null, profileEvent)), () =>
[paymentInfo, profile, profileEvent] sortMergedPaymentMethods(
mergePaymentMethods(paymentInfo, profile ?? null, effectiveProfileEvent)
),
[paymentInfo, profile, effectiveProfileEvent]
) )
const paymentMethodsByType = useMemo( const paymentMethodsByType = useMemo(
@ -151,20 +162,24 @@ export default function Profile({
) )
const hasTipDialog = useMemo( const hasTipDialog = useMemo(
() => recipientHasAnyPaymentOptions(paymentInfo, profile ?? null, profileEvent), () => recipientHasAnyPaymentOptions(paymentInfo, profile ?? null, effectiveProfileEvent),
[paymentInfo, profile, profileEvent] [paymentInfo, profile, effectiveProfileEvent]
) )
const prefetchedZapPayment = useMemo( const prefetchedZapPayment = useMemo(
() => () =>
profile?.pubkey profile?.pubkey
? buildRecipientZapPaymentData(paymentInfo, profile ?? null, profileEvent ?? null) ? buildRecipientZapPaymentData(paymentInfo, profile ?? null, effectiveProfileEvent ?? null)
: null, : null,
[paymentInfo, profile, profileEvent] [paymentInfo, profile, effectiveProfileEvent]
) )
const syncAuthorReplaceablesFromCache = useCallback(async (pubkey: string) => { const syncAuthorReplaceablesFromCache = useCallback(
async (pubkey: string, options?: { bustCache?: boolean }) => {
try { try {
if (options?.bustCache) {
replaceableEventService.clearAuthorViewPaymentAndMetadataLoaders(pubkey)
}
const [paymentEvent, metaEvent] = await Promise.all([ const [paymentEvent, metaEvent] = await Promise.all([
client.fetchPaymentInfoEvent(pubkey), client.fetchPaymentInfoEvent(pubkey),
replaceableEventService.fetchReplaceableEvent(pubkey, kinds.Metadata) replaceableEventService.fetchReplaceableEvent(pubkey, kinds.Metadata)
@ -174,7 +189,9 @@ export default function Profile({
} catch (error) { } catch (error) {
logger.error('Failed to sync author replaceables from cache', { error, pubkey }) logger.error('Failed to sync author replaceables from cache', { error, pubkey })
} }
}, []) },
[]
)
useEffect(() => { useEffect(() => {
if (!profile?.pubkey) { if (!profile?.pubkey) {
@ -195,13 +212,21 @@ export default function Profile({
void client.refreshAuthorPublishedReplaceablesOnProfileView(profile.pubkey) void client.refreshAuthorPublishedReplaceablesOnProfileView(profile.pubkey)
}, [profile?.pubkey]) }, [profile?.pubkey])
useEffect(() => {
if (!isSelf || !profile?.pubkey || !accountProfileEvent) return
setProfileEvent((prev) =>
!prev || accountProfileEvent.created_at >= prev.created_at ? accountProfileEvent : prev
)
void syncAuthorReplaceablesFromCache(profile.pubkey, { bustCache: true })
}, [isSelf, accountProfileEvent, profile?.pubkey, syncAuthorReplaceablesFromCache])
useEffect(() => { useEffect(() => {
if (!profile?.pubkey) return if (!profile?.pubkey) return
const pk = profile.pubkey.toLowerCase() const pk = profile.pubkey.toLowerCase()
const onAuthorReplaceablesRefreshed: EventListener = (domEvt) => { const onAuthorReplaceablesRefreshed: EventListener = (domEvt) => {
const detailPk = (domEvt as CustomEvent<{ pubkey?: string }>).detail?.pubkey?.toLowerCase() const detailPk = (domEvt as CustomEvent<{ pubkey?: string }>).detail?.pubkey?.toLowerCase()
if (detailPk !== pk) return if (detailPk !== pk) return
void syncAuthorReplaceablesFromCache(profile.pubkey) void syncAuthorReplaceablesFromCache(profile.pubkey, { bustCache: true })
} }
window.addEventListener( window.addEventListener(
ReplaceableEventService.AUTHOR_REPLACEABLES_REFRESHED_EVENT, ReplaceableEventService.AUTHOR_REPLACEABLES_REFRESHED_EVENT,
@ -222,8 +247,6 @@ export default function Profile({
() => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''), () => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''),
[profile] [profile]
) )
const isSelf = accountPubkey === profile?.pubkey
/** All available relays: current feed, favorites, relay sets, defaults (FAST_READ, FAST_WRITE). */ /** All available relays: current feed, favorites, relay sets, defaults (FAST_READ, FAST_WRITE). */
const allAvailableRelayUrls = useMemo(() => { const allAvailableRelayUrls = useMemo(() => {
const urls = [ const urls = [

12
src/hooks/useProfileAuthorFeedSubRequests.ts

@ -8,7 +8,7 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useNostrOptional } from '@/providers/nostr-context' import { useNostrOptional } from '@/providers/nostr-context'
import client from '@/services/client.service' import client from '@/services/client.service'
import type { TFeedSubRequest } from '@/types' import type { TFeedSubRequest } from '@/types'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
function relayListsContentKey(favoriteRelays: string[], blockedRelays: string[]): string { function relayListsContentKey(favoriteRelays: string[], blockedRelays: string[]): string {
const fav = [...favoriteRelays].map((u) => normalizeAnyRelayUrl(u) || u).filter(Boolean).sort().join('\u0001') const fav = [...favoriteRelays].map((u) => normalizeAnyRelayUrl(u) || u).filter(Boolean).sort().join('\u0001')
@ -75,10 +75,17 @@ export function useProfileAuthorFeedSubRequests({
const [refreshToken, setRefreshToken] = useState(0) const [refreshToken, setRefreshToken] = useState(0)
/** Single emission per visit: provisional→full relay stacks used to restart NoteList and wipe rows mid-fetch. */ /** Single emission per visit: provisional→full relay stacks used to restart NoteList and wipe rows mid-fetch. */
const [relayUrls, setRelayUrls] = useState<string[] | null>(null) const [relayUrls, setRelayUrls] = useState<string[] | null>(null)
const relayUrlsPubkeyRef = useRef<string | null>(null)
useEffect(() => { useEffect(() => {
let cancelled = false if (relayUrlsPubkeyRef.current !== pubkey) {
relayUrlsPubkeyRef.current = pubkey
setRelayUrls(null) setRelayUrls(null)
}
}, [pubkey])
useEffect(() => {
let cancelled = false
const socialKinds = kinds.some(isSocialKindBlockedKind) const socialKinds = kinds.some(isSocialKindBlockedKind)
void client void client
@ -115,6 +122,7 @@ export function useProfileAuthorFeedSubRequests({
}, [authorHex, kindsKey, limit]) }, [authorHex, kindsKey, limit])
const refresh = useCallback(() => { const refresh = useCallback(() => {
setRelayUrls(null)
setRefreshToken((n) => n + 1) setRefreshToken((n) => n + 1)
}, []) }, [])

15
src/providers/NostrProvider/index.tsx

@ -563,6 +563,9 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
} }
const sortedEvents = events.sort((a, b) => b.created_at - a.created_at) const sortedEvents = events.sort((a, b) => b.created_at - a.created_at)
const profileEvent = sortedEvents.find((e) => e.kind === kinds.Metadata) const profileEvent = sortedEvents.find((e) => e.kind === kinds.Metadata)
const paymentInfoEvent = sortedEvents
.filter((e) => e.kind === ExtendedKind.PAYMENT_INFO)
.sort((a, b) => b.created_at - a.created_at)[0]
const followListEvent = sortedEvents.find((e) => e.kind === kinds.Contacts) const followListEvent = sortedEvents.find((e) => e.kind === kinds.Contacts)
const muteListEvent = sortedEvents.find((e) => e.kind === kinds.Mutelist) const muteListEvent = sortedEvents.find((e) => e.kind === kinds.Mutelist)
const bookmarkListEvent = sortedEvents.find((e) => e.kind === kinds.BookmarkList) const bookmarkListEvent = sortedEvents.find((e) => e.kind === kinds.BookmarkList)
@ -585,6 +588,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
const [ const [
resolvedProfilePut, resolvedProfilePut,
resolvedPaymentPut,
resolvedFollowPut, resolvedFollowPut,
resolvedMutePut, resolvedMutePut,
resolvedBookmarkPut, resolvedBookmarkPut,
@ -594,6 +598,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
resolvedUserEmojiPut resolvedUserEmojiPut
] = await Promise.all([ ] = await Promise.all([
safePutReplaceable(profileEvent), safePutReplaceable(profileEvent),
safePutReplaceable(paymentInfoEvent),
safePutReplaceable(followListEvent), safePutReplaceable(followListEvent),
safePutReplaceable(muteListEvent), safePutReplaceable(muteListEvent),
safePutReplaceable(bookmarkListEvent), safePutReplaceable(bookmarkListEvent),
@ -626,6 +631,16 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
username: formatPubkey(account.pubkey) username: formatPubkey(account.pubkey)
}) })
} }
if (paymentInfoEvent) {
const resolvedPayment = resolvedPaymentPut ?? paymentInfoEvent
try {
await replaceableEventService.updateReplaceableEventCache(resolvedPayment)
} catch {
try {
await replaceableEventService.updateReplaceableEventCache(paymentInfoEvent)
} catch {}
}
}
if (followListEvent) { if (followListEvent) {
if (resolvedFollowPut && resolvedFollowPut.id === followListEvent.id) { if (resolvedFollowPut && resolvedFollowPut.id === followListEvent.id) {
setFollowListEvent(followListEvent) setFollowListEvent(followListEvent)

8
src/services/client-replaceable-events.service.ts

@ -1373,6 +1373,14 @@ export class ReplaceableEventService {
return await this.fetchReplaceableEvent(pubkey, ExtendedKind.PAYMENT_INFO) return await this.fetchReplaceableEvent(pubkey, ExtendedKind.PAYMENT_INFO)
} }
/** Drop in-memory kind 0 / 10133 loaders so the next read picks up IndexedDB after a profile-view refresh. */
clearAuthorViewPaymentAndMetadataLoaders(pubkey: string): void {
const pk = pubkey.trim().toLowerCase()
if (!/^[0-9a-f]{64}$/.test(pk)) return
this.replaceableEventFromBigRelaysDataloader.clear({ pubkey: pk, kind: kinds.Metadata })
this.replaceableEventFromBigRelaysDataloader.clear({ pubkey: pk, kind: ExtendedKind.PAYMENT_INFO })
}
/** /**
* Force refresh profile and payment info: clear in-memory loaders, pull from relays (incl. 10133), persist to IndexedDB. * Force refresh profile and payment info: clear in-memory loaders, pull from relays (incl. 10133), persist to IndexedDB.
*/ */

Loading…
Cancel
Save