Browse Source

bug-fixes

imwald
Silberengel 2 weeks ago
parent
commit
b9f213d79b
  1. 10
      src/components/AccountList/index.tsx
  2. 7
      src/components/AccountQuickSwitchMenuItems.tsx
  3. 59
      src/components/ReplyNoteList/index.tsx
  4. 3
      src/components/StoredAccountSwitchSelect.tsx
  5. 12
      src/constants.ts
  6. 11
      src/lib/relay-icon-source.test.ts
  7. 25
      src/lib/relay-icon-source.ts
  8. 18
      src/lib/relay-list-builder.ts

10
src/components/AccountList/index.tsx

@ -57,13 +57,13 @@ export default function AccountList({ @@ -57,13 +57,13 @@ export default function AccountList({
if (isRedundantAccountPick(act, account)) {
if (account?.signerType === 'npub' && act.signerType === 'nip-07') {
setSwitchingAccount(act)
const switched = await switchAccount(act)
if (switched) {
await switchAccount(act)
const ok = await retryNip07SignerForPreferredAccount()
if (ok) {
toast.success(t('accountSwitch.extensionConnected'))
afterSwitch()
} else {
const ok = await retryNip07SignerForPreferredAccount()
if (ok) toast.success(t('accountSwitch.extensionConnected'))
else toast.error(t('accountSwitch.extensionRetryFailed'))
toast.error(t('accountSwitch.extensionRetryFailed'))
}
setSwitchingAccount(null)
}

7
src/components/AccountQuickSwitchMenuItems.tsx

@ -50,11 +50,8 @@ export function AccountQuickSwitchMenuItems({ onAfterSwitch }: { onAfterSwitch?: @@ -50,11 +50,8 @@ export function AccountQuickSwitchMenuItems({ onAfterSwitch }: { onAfterSwitch?:
if (isRedundantAccountPick(act, account)) {
if (account?.signerType === 'npub' && act.signerType === 'nip-07') {
const switched = await switchAccount(act)
if (switched) {
onAfterSwitch?.()
return
}
// switchAccount may return a pubkey even when it fell back to read-only npub — always try reconnect.
await switchAccount(act)
const ok = await retryNip07SignerForPreferredAccount()
if (ok) {
toast.success(t('accountSwitch.extensionConnected'))

59
src/components/ReplyNoteList/index.tsx

@ -490,6 +490,8 @@ function ReplyNoteList({ @@ -490,6 +490,8 @@ function ReplyNoteList({
])
const [loading, setLoading] = useState<boolean>(false)
/** Bumped when thread relay URLs are known — re-runs stats id hydration with inbox relays. */
const [threadRelaysRevision, setThreadRelaysRevision] = useState(0)
const [showCount, setShowCount] = useState(THREAD_REPLY_SHOW_COUNT)
const [highlightReplyId, setHighlightReplyId] = useState<string | undefined>(undefined)
const replyRefs = useRef<Record<string, HTMLDivElement | null>>({})
@ -500,6 +502,7 @@ function ReplyNoteList({ @@ -500,6 +502,7 @@ function ReplyNoteList({
useEffect(() => {
statsHydratedReplyIdsRef.current.clear()
setThreadRelaysRevision(0)
}, [event.id])
useEffect(() => {
@ -520,11 +523,14 @@ function ReplyNoteList({ @@ -520,11 +523,14 @@ function ReplyNoteList({
)
if (candidates.length === 0) return
const relayUrls = threadRelayUrlsRef.current
if (!relayUrls.length) return
let cancelled = false
;(async () => {
for (const { id } of candidates) statsHydratedReplyIdsRef.current.add(id)
const batch = await hydrateThreadRepliesFromStats(candidates, {
relayUrls: threadRelayUrlsRef.current,
relayUrls,
mutePubkeySet,
hideContentMentioningMutedUsers
})
@ -547,7 +553,8 @@ function ReplyNoteList({ @@ -547,7 +553,8 @@ function ReplyNoteList({
addReplies,
mutePubkeySet,
hideContentMentioningMutedUsers,
refreshToken
refreshToken,
threadRelaysRevision
])
/** When stats counted many replies but the thread REQ returned few, run the same social filters as note-stats. */
@ -695,12 +702,17 @@ function ReplyNoteList({ @@ -695,12 +702,17 @@ function ReplyNoteList({
// Check cache next — discussion cache merges with relay results
const cachedData = discussionFeedCache.getCachedReplies(rootInfo)
const hasCache = cachedData !== null
const existingReplyCount = [...repliesMap.values()].reduce((n, b) => n + b.events.length, 0)
const showLoadingIndicator =
existingReplyCount === 0 && !(hasCache && cachedData && cachedData.length > 0)
if (hasCache && cachedData) {
addReplies(cachedData)
setLoading(false)
} else {
}
if (showLoadingIndicator) {
setLoading(true)
} else {
setLoading(false)
}
try {
@ -727,6 +739,7 @@ function ReplyNoteList({ @@ -727,6 +739,7 @@ function ReplyNoteList({
async function fetchFromRelays() {
if (!rootInfo) return // Type guard
const streamWalk = new Map<string, NEvent>()
try {
// READ from: thread hints, author/user NIP-65, favorites, cache — then DEFAULT_FAVORITE_RELAYS fallback.
const opAuthorPubkey = rootInfo.type === 'E' || rootInfo.type === 'A' ? rootInfo.pubkey : undefined
@ -789,6 +802,7 @@ function ReplyNoteList({ @@ -789,6 +802,7 @@ function ReplyNoteList({
)
)
threadRelayUrlsRef.current = relayUrlsForThreadReq
setThreadRelaysRevision((n) => n + 1)
const recipientPubkey = event.pubkey
// Stream replies as relays return them (aggr is first in the list) instead of waiting for full EOSE.
@ -804,16 +818,17 @@ function ReplyNoteList({ @@ -804,16 +818,17 @@ function ReplyNoteList({
}
if (shouldHideThreadResponseEvent(evt, mutePubkeySet, hideContentMentioningMutedUsers))
return
if (statsIdsStream.size > 0) {
if (
!statsIdsStream.has(evt.id) &&
!replyMatchesThreadForList(evt, event, rootInfo, isDiscussionRoot)
) {
streamWalk.set(evt.id.toLowerCase(), evt)
if (statsIdsStream.has(evt.id)) {
addReplies([evt])
setLoading(false)
return
}
if (!replyMatchesThreadForList(evt, event, rootInfo, isDiscussionRoot, streamWalk)) {
return
}
addReplies([evt])
if (!hasCache) setLoading(false)
setLoading(false)
}
const superchatFilters = buildThreadSuperchatPriorityFilters({
@ -941,9 +956,22 @@ function ReplyNoteList({ @@ -941,9 +956,22 @@ function ReplyNoteList({
}, 0)
}
if (!hasCache) {
// No cache: stop loading after adding replies
setLoading(false)
const statsAfterFetch = noteStatsService.getNoteStats(event.id)?.replies
if (statsAfterFetch?.length) {
const resolvedIds = new Set(mergedForUi.map((e) => e.id))
const missingStats = statsAfterFetch.filter(
(r) => !resolvedIds.has(r.id) && !client.peekSessionCachedEvent(r.id)
)
if (missingStats.length > 0) {
const hydrated = await hydrateThreadRepliesFromStats(missingStats, {
relayUrls: relayUrlsForThreadReq,
mutePubkeySet,
hideContentMentioningMutedUsers
})
if (fetchGeneration === replyFetchGenRef.current && hydrated.length > 0) {
addReplies(hydrated)
}
}
}
// Second pass for URL threads: fetch replies to individual comments that may omit the
@ -1068,9 +1096,8 @@ function ReplyNoteList({ @@ -1068,9 +1096,8 @@ function ReplyNoteList({
}
} catch (error) {
logger.error('[ReplyNoteList] Error fetching replies:', error)
if (fetchGeneration !== replyFetchGenRef.current) return
if (!hasCache) {
// Only set loading to false if we don't have cache to fall back on
} finally {
if (fetchGeneration === replyFetchGenRef.current) {
setLoading(false)
}
}

3
src/components/StoredAccountSwitchSelect.tsx

@ -161,8 +161,7 @@ export default function StoredAccountSwitchSelect({ @@ -161,8 +161,7 @@ export default function StoredAccountSwitchSelect({
if (account?.signerType === 'npub' && nextAccount.signerType === 'nip-07') {
setSwitchingKey(accountPointerKey(nextAccount))
try {
const switched = await switchAccount(nextAccount)
if (switched) return
await switchAccount(nextAccount)
const ok = await retryNip07SignerForPreferredAccount()
if (ok) toast.success(t('accountSwitch.extensionConnected'))
else toast.error(t('accountSwitch.extensionRetryFailed'))

12
src/constants.ts

@ -515,7 +515,8 @@ export const FAST_READ_RELAY_URLS = [ @@ -515,7 +515,8 @@ export const FAST_READ_RELAY_URLS = [
'wss://nostr.land',
'wss://nostr.wine',
'wss://nostr21.com',
'wss://primus.nostr1.com'
'wss://primus.nostr1.com',
'wss://nostr.sovbit.host'
]
// Optimized relay list for write operations (no aggregator since it's read-only)
@ -557,15 +558,8 @@ export const NOSTR_ARCHIVES_API_RATE_LIMIT_PER_MIN = 100 @@ -557,15 +558,8 @@ export const NOSTR_ARCHIVES_API_RATE_LIMIT_PER_MIN = 100
export const SEARCHABLE_RELAY_URLS = [
NOSTR_ARCHIVES_SEARCH_RELAY_URL,
'wss://search.nos.today',
'wss://nostr.wine',
'wss://relay.noswhere.com',
'wss://nostr-pub.wellorder.net',
'wss://relay.damus.io',
'wss://theforest.nostr1.com',
'wss://nostr.land',
'wss://relay.primal.net',
'wss://nos.lol',
'wss://thecitadel.nostr1.com'
'wss://nostr-pub.wellorder.net'
]
/**

11
src/lib/relay-icon-source.test.ts

@ -1,5 +1,10 @@ @@ -1,5 +1,10 @@
import { NOSTR_ARCHIVES_SEARCH_RELAY_URL } from '@/constants'
import { describe, expect, it } from 'vitest'
import { getRelayIconFallbackGlyph, getRelayIconOverrideSrc } from '@/lib/relay-icon-source'
import {
getRelayIconFallbackGlyph,
getRelayIconOverrideSrc,
NOSTRARCHIVES_SITE_ICON_SRC
} from '@/lib/relay-icon-source'
describe('relay icon branding', () => {
it('uses favicon override for sovbit hosts', () => {
@ -11,4 +16,8 @@ describe('relay icon branding', () => { @@ -11,4 +16,8 @@ describe('relay icon branding', () => {
expect(getRelayIconFallbackGlyph('wss://purplepag.es/')).toBe('🟣')
expect(getRelayIconOverrideSrc('wss://purplepag.es/')).toBeUndefined()
})
it('uses nostrarchives favicon for search relay (same as trending)', () => {
expect(getRelayIconOverrideSrc(NOSTR_ARCHIVES_SEARCH_RELAY_URL)).toBe(NOSTRARCHIVES_SITE_ICON_SRC)
})
})

25
src/lib/relay-icon-source.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { NOSTR_ARCHIVES_SEARCH_RELAY_URL } from '@/constants'
import { normalizeUrl } from '@/lib/url'
import { isWispTrendingNotesRelayUrl } from '@/lib/wisp-trending-relay'
@ -14,11 +15,27 @@ export const NOSTR_SOVBIT_ICON_SRC = 'https://nostr.sovbit.host/favicon.ico' @@ -14,11 +15,27 @@ export const NOSTR_SOVBIT_ICON_SRC = 'https://nostr.sovbit.host/favicon.ico'
export const FREELAY_SOVBIT_ICON_SRC = 'https://freelay.sovbit.host/favicon.ico'
/**
* Nostr Archives front-site favicon for trending shards and related relay hosts.
* Nostr Archives front-site favicon for trending shards, search relay, and related hosts.
* @see https://nostrarchives.com/
*/
export const NOSTRARCHIVES_SITE_ICON_SRC = 'https://nostrarchives.com/favicon.ico'
/** Same branding as Wisp trending — nostrarchives.com favicon in {@link RelayIcon}. */
export function isNostrArchivesBrandedRelayUrl(url: string | undefined): boolean {
if (!url) return false
if (isWispTrendingNotesRelayUrl(url)) return true
const norm = (normalizeUrl(url) || url).trim().toLowerCase()
if (norm === (normalizeUrl(NOSTR_ARCHIVES_SEARCH_RELAY_URL) || NOSTR_ARCHIVES_SEARCH_RELAY_URL).toLowerCase()) {
return true
}
const host = parseRelayHostname(url)
return (
host === 'feeds.nostrarchives.com' ||
host === 'nostrarchives.com' ||
host === 'search.nostrarchives.com'
)
}
function parseRelayHostname(url: string): string | undefined {
const raw = (normalizeUrl(url) || url).trim()
const forParse = raw.replace(/^ws:\/\//i, 'http://').replace(/^wss:\/\//i, 'https://')
@ -43,11 +60,7 @@ export function getRelayIconOverrideSrc(url: string | undefined): string | undef @@ -43,11 +60,7 @@ export function getRelayIconOverrideSrc(url: string | undefined): string | undef
if (host === 'freelay.sovbit.host') {
return FREELAY_SOVBIT_ICON_SRC
}
if (
isWispTrendingNotesRelayUrl(url) ||
host === 'feeds.nostrarchives.com' ||
host === 'nostrarchives.com'
) {
if (isNostrArchivesBrandedRelayUrl(url)) {
return NOSTRARCHIVES_SITE_ICON_SRC
}
return undefined

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

@ -13,6 +13,7 @@ import { FAST_READ_RELAY_URLS, PROFILE_RELAY_URLS, SEARCHABLE_RELAY_URLS } from @@ -13,6 +13,7 @@ import { FAST_READ_RELAY_URLS, PROFILE_RELAY_URLS, SEARCHABLE_RELAY_URLS } from
import { getHttpRelayListFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
import { feedRelayPolicyUrls } from '@/features/feed/relay-policy'
import { mergeRelayUrlLayers, userReadRelaysWithHttp } from '@/lib/favorites-feed-relays'
import { collectUserReadInboxUrls } from '@/lib/viewer-read-inboxes'
import { isRelayBlockedByUser } from '@/lib/relay-blocked'
import { prependAggrForEventLookupRelayUrls } from '@/lib/nostr-land-relay-eligibility'
import { urlIsNonLocalForRemoteViewer } from '@/lib/relay-list-sanitize'
@ -737,7 +738,18 @@ export async function buildReplyReadRelayList( @@ -737,7 +738,18 @@ export async function buildReplyReadRelayList(
includeProfileFetchRelays: useGlobal,
blockedRelays
})
return prependAggrForEventLookupRelayUrls(
mergeRelayUrlLayers([scoped, defaultFavoriteRelaysForViewer(useGlobal)], blockedRelays)
)
const layers: string[][] = [threadRelayHints]
if (userPubkey) {
try {
const rl = await client.peekRelayListFromStorage(userPubkey)
const cache = await getCacheRelayUrls(userPubkey).catch(() => [] as string[])
const inbox = collectUserReadInboxUrls(rl ?? undefined, cache)
if (inbox.length > 0) layers.push(inbox)
} catch {
/* inbox tier optional */
}
}
layers.push(scoped)
layers.push(defaultFavoriteRelaysForViewer(useGlobal))
return prependAggrForEventLookupRelayUrls(mergeRelayUrlLayers(layers, blockedRelays))
}

Loading…
Cancel
Save