Browse Source

bug-fixes

imwald
Silberengel 4 weeks ago
parent
commit
7f99112e7a
  1. 4
      src/PageManager.tsx
  2. 14
      src/components/NoteStats/ZapButton.tsx
  3. 4
      src/components/ProfileOptions/index.tsx
  4. 2
      src/constants.ts
  5. 2
      src/pages/secondary/ProfileEditorPage/index.tsx
  6. 31
      src/services/client-replaceable-events.service.ts
  7. 8
      src/services/client.service.ts

4
src/PageManager.tsx

@ -69,6 +69,8 @@ import { SecondaryPageContext, useSecondaryPage, useSecondaryPageOptional } from @@ -69,6 +69,8 @@ import { SecondaryPageContext, useSecondaryPage, useSecondaryPageOptional } from
/** Survives React StrictMode remount so initial URL → secondary stack is not built twice. */
let historyLocationSeedApplied = false
/** Dedupes note URL seed when React runs the history effect twice before state commits. */
let historyNoteStackSeedUrl: string | null = null
/** Lazy-loaded so PageManager does not synchronously import SpellsPage (avoids HMR cycle: SpellsPage → PrimaryPageLayout → PageManager → SpellsPage). */
const SpellsPageLazy = lazy(() => import('./pages/primary/SpellsPage'))
@ -1342,6 +1344,8 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -1342,6 +1344,8 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
let primaryForNoteUrl: TPrimaryPageName = currentPrimaryPage
const pushNoteUrlOnStack = (noteUrl: string) => {
if (historyNoteStackSeedUrl === noteUrl) return
historyNoteStackSeedUrl = noteUrl
setSecondaryStack((prevStack) => {
if (isCurrentPage(prevStack, noteUrl)) return prevStack
const { newStack, newItem } = pushNewPageToStack(prevStack, noteUrl, maxStackSize)

14
src/components/NoteStats/ZapButton.tsx

@ -54,12 +54,10 @@ async function resolveZapRecipientData( @@ -54,12 +54,10 @@ async function resolveZapRecipientData(
feedProfile && !feedProfile.batchPlaceholder ? feedProfile : null
const deferNetwork = shouldDeferPerPubkeyProfileNetwork(authorPubkey)
const paymentPromise = deferNetwork
? replaceableEventService.getPaymentInfoFromIndexedDB(authorPubkey)
: client.fetchPaymentInfoEvent(authorPubkey)
if (cachedFeed) {
const paymentEvent = await paymentPromise.catch(() => undefined)
const paymentEvent = deferNetwork
? await replaceableEventService.getPaymentInfoFromIndexedDB(authorPubkey).catch(() => undefined)
: await client.fetchPaymentInfoEvent(authorPubkey).catch(() => undefined)
return {
profile: cachedFeed,
profileEvent: undefined,
@ -70,7 +68,9 @@ async function resolveZapRecipientData( @@ -70,7 +68,9 @@ async function resolveZapRecipientData(
const idbProfile = await replaceableEventService.getProfileFromIndexedDB(authorPubkey)
if (deferNetwork) {
const paymentEvent = await paymentPromise.catch(() => undefined)
const paymentEvent = await replaceableEventService
.getPaymentInfoFromIndexedDB(authorPubkey)
.catch(() => undefined)
return {
profile: idbProfile ?? null,
profileEvent: undefined,
@ -80,7 +80,7 @@ async function resolveZapRecipientData( @@ -80,7 +80,7 @@ async function resolveZapRecipientData(
const [profileRes, paymentRes] = await Promise.allSettled([
replaceableEventService.fetchReplaceableEvent(authorPubkey, kinds.Metadata),
paymentPromise
client.fetchPaymentInfoEvent(authorPubkey)
])
const profileEvent =
profileRes.status === 'fulfilled' ? profileRes.value : undefined

4
src/components/ProfileOptions/index.tsx

@ -84,7 +84,9 @@ export default function ProfileOptions({ @@ -84,7 +84,9 @@ export default function ProfileOptions({
const fetchEvent = async () => {
try {
// Use fetchProfileEvent which includes comprehensive relay search
const event = await replaceableEventService.fetchProfileEvent(pubkey, false)
const event = await replaceableEventService.fetchProfileEvent(pubkey, false, {
allowWideRelayFallback: true
})
if (event) {
setLocalProfileEvent(event)
}

2
src/constants.ts

@ -267,7 +267,7 @@ export const PROFILE_BATCH_NETWORK_LOAD_TIMEOUT_MS = 12_000 @@ -267,7 +267,7 @@ export const PROFILE_BATCH_NETWORK_LOAD_TIMEOUT_MS = 12_000
* After a feed/thread profile batch finishes, block per-row metadata/payment relay REQs so
* {@link ZapButton} and {@link useFetchProfile} do not fan out hundreds of parallel queries.
*/
export const PROFILE_BATCH_POST_COOLDOWN_MS = 45_000
export const PROFILE_BATCH_POST_COOLDOWN_MS = 90_000
/**
* Hex-id / replaceable-coordinate note lookup ({@link EventService.tryHarderToFetchEvent}, big-relays dataloader).

2
src/pages/secondary/ProfileEditorPage/index.tsx

@ -297,7 +297,7 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => { @@ -297,7 +297,7 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
await syncUserDeletionTombstones(account.pubkey, relayList)
await client.forceRefreshProfileAndPaymentInfoCache(account.pubkey)
const [profileEvt, paymentEvt] = await Promise.all([
client.fetchProfileEvent(account.pubkey),
client.fetchProfileEvent(account.pubkey, false, { allowWideRelayFallback: true }),
client.fetchPaymentInfoEvent(account.pubkey)
])
if (profileEvt) {

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

@ -41,6 +41,14 @@ import { @@ -41,6 +41,14 @@ import {
import { isPromiseTimeoutError, racePromiseWithTimeout } from '@/lib/async-timeout'
import { networkKindsForReplaceableFetch } from '@/lib/replaceable-fetch-kinds'
export type FetchProfileEventOptions = {
/**
* When false (default), stop after batched profile relays / DataLoader no per-author
* NIP-65 + expanded-relay REQ (avoids hundreds of parallel 7-relay queries on feed paint).
*/
allowWideRelayFallback?: boolean
}
export class ReplaceableEventService {
/** Limits parallel {@link fetchKind0FromProfileRelays} (7-relay REQ per author). */
private static kind0ProfileRelaySlotsInUse = 0
@ -249,7 +257,9 @@ export class ReplaceableEventService { @@ -249,7 +257,9 @@ export class ReplaceableEventService {
if (pick) {
this.replaceableEventFromBigRelaysDataloader.prime({ pubkey, kind }, Promise.resolve(pick))
void indexedDb.putReplaceableEvent(pick).catch(() => {})
if (!shouldDeferPerPubkeyProfileNetwork(pubkey)) {
void this.refreshInBackground(pubkey, kind, d).catch(() => {})
}
return pick
}
}
@ -333,6 +343,7 @@ export class ReplaceableEventService { @@ -333,6 +343,7 @@ export class ReplaceableEventService {
* Refresh event in background (non-blocking)
*/
private async refreshInBackground(pubkey: string, kind: number, d?: string): Promise<void> {
if (shouldDeferPerPubkeyProfileNetwork(pubkey)) return
try {
if (d) {
await this.replaceableEventDataLoader.load({ pubkey, kind, d })
@ -943,7 +954,12 @@ export class ReplaceableEventService { @@ -943,7 +954,12 @@ export class ReplaceableEventService {
/**
* Fetch profile event by id (hex, npub, nprofile)
*/
async fetchProfileEvent(id: string, _skipCache: boolean = false): Promise<NEvent | undefined> {
async fetchProfileEvent(
id: string,
_skipCache: boolean = false,
options: FetchProfileEventOptions = {}
): Promise<NEvent | undefined> {
const allowWideRelayFallback = options.allowWideRelayFallback === true
let pubkey: string | undefined
let relays: string[] = []
if (/^[0-9a-f]{64}$/.test(id)) {
@ -983,7 +999,9 @@ export class ReplaceableEventService { @@ -983,7 +999,9 @@ export class ReplaceableEventService {
)
await this.indexProfile(sessionEv)
void indexedDb.putReplaceableEvent(sessionEv).catch(() => {})
if (!shouldDeferPerPubkeyProfileNetwork(pubkey)) {
void this.refreshInBackground(pubkey, kinds.Metadata).catch(() => {})
}
return sessionEv
}
try {
@ -994,7 +1012,9 @@ export class ReplaceableEventService { @@ -994,7 +1012,9 @@ export class ReplaceableEventService {
Promise.resolve(idbEv)
)
await this.indexProfile(idbEv)
if (!shouldDeferPerPubkeyProfileNetwork(pubkey)) {
void this.refreshInBackground(pubkey, kinds.Metadata).catch(() => {})
}
return idbEv
}
} catch {
@ -1015,10 +1035,10 @@ export class ReplaceableEventService { @@ -1015,10 +1035,10 @@ export class ReplaceableEventService {
return sessionFallback
}
// Relay hints from bech32 (nprofile, etc.) — highest priority in later steps
// Relay hints from bech32 (nprofile, etc.) — highest priority in wide fallback only
const relayHints = relays.length > 0 ? [...relays] : []
// Step 0: {@link PROFILE_RELAY_URLS} by `authors` — after local caches miss.
if (allowWideRelayFallback) {
const fromProfileRelays = await this.fetchKind0FromProfileRelays(pubkey)
if (fromProfileRelays) {
this.replaceableEventFromBigRelaysDataloader.prime(
@ -1029,6 +1049,7 @@ export class ReplaceableEventService { @@ -1029,6 +1049,7 @@ export class ReplaceableEventService {
void indexedDb.putReplaceableEvent(fromProfileRelays).catch(() => {})
return fromProfileRelays
}
}
// Step 1: DataLoader (IndexedDB + batched profile relay stack)
// CRITICAL: Do NOT pass relay hints here - passing any relays bypasses DataLoader and creates individual subscriptions
@ -1043,6 +1064,10 @@ export class ReplaceableEventService { @@ -1043,6 +1064,10 @@ export class ReplaceableEventService {
return profileEvent
}
if (!allowWideRelayFallback) {
return sessionFallback
}
await ReplaceableEventService.acquireProfileFallbackNetworkSlot()
try {
// Step 2: Only after cache + default relays miss — NIP-65 relay list (timeout-capped), then hints + outbox/inbox + defaults.

8
src/services/client.service.ts

@ -4105,8 +4105,12 @@ class ClientService extends EventTarget { @@ -4105,8 +4105,12 @@ class ClientService extends EventTarget {
}
// Delegate to ReplaceableEventService
async fetchProfileEvent(id: string, skipCache: boolean = false): Promise<NEvent | undefined> {
return this.replaceableEventService.fetchProfileEvent(id, skipCache)
async fetchProfileEvent(
id: string,
skipCache: boolean = false,
options?: import('./client-replaceable-events.service').FetchProfileEventOptions
): Promise<NEvent | undefined> {
return this.replaceableEventService.fetchProfileEvent(id, skipCache, options)
}
async fetchProfile(id: string, skipCache: boolean = false): Promise<TProfile | undefined> {

Loading…
Cancel
Save