{visibleMutePubkeys.map((pubkey, index) => (
diff --git a/src/pages/secondary/OthersRelaySettingsPage/index.tsx b/src/pages/secondary/OthersRelaySettingsPage/index.tsx
index 58ed14d6..38fdb700 100644
--- a/src/pages/secondary/OthersRelaySettingsPage/index.tsx
+++ b/src/pages/secondary/OthersRelaySettingsPage/index.tsx
@@ -1,8 +1,20 @@
+import JsonViewDialog from '@/components/JsonViewDialog'
import OthersRelayList from '@/components/OthersRelayList'
import { RefreshButton } from '@/components/RefreshButton'
-import { useFetchProfile } from '@/hooks'
+import { Button } from '@/components/ui/button'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from '@/components/ui/dropdown-menu'
+import { ExtendedKind } from '@/constants'
+import { useFetchProfile, useFetchRelayList } from '@/hooks'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
+import indexedDb from '@/services/indexed-db.service'
+import { Code, MoreVertical } from 'lucide-react'
+import { kinds } from 'nostr-tools'
import { forwardRef, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -10,10 +22,37 @@ const RelaySettingsPage = forwardRef(({ id, index, hideTitlebar = false }: { id?
const { t } = useTranslation()
const { registerPrimaryPanelRefresh } = usePrimaryNoteView()
const { profile } = useFetchProfile(id)
+ const { relayList } = useFetchRelayList(profile?.pubkey)
const [listKey, setListKey] = useState(0)
+ const [jsonOpen, setJsonOpen] = useState(false)
+ const [jsonPayload, setJsonPayload] = useState
(null)
const bumpList = useCallback(() => setListKey((k) => k + 1), [])
+ const openRelayListJson = useCallback(async () => {
+ const pk = profile?.pubkey
+ if (!pk) {
+ setJsonPayload({ error: 'No profile pubkey' })
+ setJsonOpen(true)
+ return
+ }
+ const [k10002, k10432, k10243] = await Promise.all([
+ indexedDb.getReplaceableEvent(pk, kinds.RelayList).catch(() => null),
+ indexedDb.getReplaceableEvent(pk, ExtendedKind.CACHE_RELAYS).catch(() => null),
+ indexedDb.getReplaceableEvent(pk, ExtendedKind.HTTP_RELAY_LIST).catch(() => null)
+ ])
+ setJsonPayload({
+ pubkey: pk,
+ mergedRelayList: relayList,
+ kind10002_mailbox_fromIndexedDb: k10002 ?? null,
+ kind10432_cacheRelays_fromIndexedDb: k10432 ?? null,
+ kind10243_httpRelayList_fromIndexedDb: k10243 ?? null,
+ note:
+ 'Merged list is from the network/cache service. IndexedDB events appear only if this pubkey’s replaceable lists were stored locally (e.g. after a profile or relay fetch).'
+ })
+ setJsonOpen(true)
+ }, [profile?.pubkey, relayList])
+
useEffect(() => {
if (!hideTitlebar) {
registerPrimaryPanelRefresh(null)
@@ -33,8 +72,28 @@ const RelaySettingsPage = forwardRef(({ id, index, hideTitlebar = false }: { id?
index={index}
title={hideTitlebar ? undefined : t("username's used relays", { username: profile.username })}
hideBackButton={hideTitlebar}
- controls={hideTitlebar ? undefined : }
+ controls={
+ hideTitlebar ? undefined : (
+
+
+
+
+
+
+
+ void openRelayListJson()}>
+
+ {t('View JSON')}
+
+
+
+
+ )
+ }
>
+ setJsonOpen(false)} />
diff --git a/src/pages/secondary/RelayReviewsPage/index.tsx b/src/pages/secondary/RelayReviewsPage/index.tsx
index 06722e69..a3e47222 100644
--- a/src/pages/secondary/RelayReviewsPage/index.tsx
+++ b/src/pages/secondary/RelayReviewsPage/index.tsx
@@ -4,6 +4,7 @@ import { RefreshButton } from '@/components/RefreshButton'
import { FAST_READ_RELAY_URLS, ExtendedKind } from '@/constants'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
+import { relayReviewDTagsForRelayUrl, relayReviewsFeedSnapshotKey } from '@/lib/relay-review-feed'
import { normalizeUrl, simplifyUrl } from '@/lib/url'
import type { TFeedSubRequest } from '@/types'
import { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react'
@@ -26,23 +27,14 @@ const RelayReviewsPage = forwardRef(({ url, index, hideTitlebar = false }: { url
}, [hideTitlebar, registerPrimaryPanelRefresh, bumpFeed])
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
- /** `d` tag values vary by client (raw vs normalized URL); REQ should OR-match like {@link RelayReviewsPreview}. */
- const relayReviewDTags = useMemo(() => {
- const raw = url?.trim()
- const norm = normalizedUrl?.trim()
- const uniq: string[] = []
- const add = (s: string | undefined) => {
- const t = s?.trim()
- if (t && !uniq.includes(t)) uniq.push(t)
- }
- add(raw)
- add(norm)
- return uniq
- }, [url, normalizedUrl])
+ /** `d` tag values vary by client (raw vs normalized URL); REQ must OR-match every variant. */
+ const relayReviewDTags = useMemo(
+ () => (url ? relayReviewDTagsForRelayUrl(url) : []),
+ [url]
+ )
/** Stable identity for session feed snapshot (decoupled from FAST_READ_RELAY_URLS JSON churn). */
const relayReviewsFeedSubscriptionKey = useMemo(
- () =>
- normalizedUrl ? `relay-reviews:v1|${normalizedUrl}|k=${ExtendedKind.RELAY_REVIEW}` : '',
+ () => (normalizedUrl ? relayReviewsFeedSnapshotKey(normalizedUrl) : ''),
[normalizedUrl]
)
const reviewsSubRequests = useMemo(() => {
diff --git a/src/pages/secondary/RelaySettingsPage/index.tsx b/src/pages/secondary/RelaySettingsPage/index.tsx
index 45a936e7..fdbda04d 100644
--- a/src/pages/secondary/RelaySettingsPage/index.tsx
+++ b/src/pages/secondary/RelaySettingsPage/index.tsx
@@ -1,11 +1,24 @@
import HttpRelaysSetting from '@/components/HttpRelaysSetting'
+import JsonViewDialog from '@/components/JsonViewDialog'
import MailboxSetting from '@/components/MailboxSetting'
import FavoriteRelaysSetting from '@/components/FavoriteRelaysSetting'
import SessionRelaysTab from '@/components/SessionRelaysTab'
import { RefreshButton } from '@/components/RefreshButton'
+import { Button } from '@/components/ui/button'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from '@/components/ui/dropdown-menu'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { ExtendedKind } from '@/constants'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
+import { useNostr } from '@/providers/NostrProvider'
+import indexedDb from '@/services/indexed-db.service'
+import { Code, MoreVertical } from 'lucide-react'
+import { kinds } from 'nostr-tools'
import { forwardRef, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -47,8 +60,28 @@ const RelaySettingsPage = forwardRef(({ index, hideTitlebar = false }: { index?:
ref={ref}
index={index}
title={hideTitlebar ? undefined : t('Relays and Storage Settings')}
- controls={hideTitlebar ? undefined : }
+ controls={
+ hideTitlebar ? undefined : (
+
+
+
+
+
+
+
+ void openRelayListJson()}>
+
+ {t('View JSON')}
+
+
+
+
+ )
+ }
>
+ setJsonOpen(false)} />
{t('Favorite Relays')}
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index 91303f66..f29c8498 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -32,7 +32,7 @@ function canonicalSeenOnEventId(eventId: string): string {
import { shouldDropEventOnIngest } from '@/lib/event-ingest-filter'
import { getHttpRelayListFromEvent, getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata'
import logger from '@/lib/logger'
-import { installNostrRelayAuthRaceMitigation } from '@/lib/nostr-relay-auth-patch'
+import { patchPoolRelayAuthRaceAndFeedback } from '@/lib/nostr-relay-auth-patch'
import { queueRelayAuthSign } from '@/lib/relay-auth-sign-queue'
import {
authenticateNip42Relay,
@@ -217,7 +217,6 @@ class ClientService extends EventTarget {
constructor() {
super()
- installNostrRelayAuthRaceMitigation()
this.pool = new SimplePool()
this.pool.trackRelays = true
const rawEnsureRelay = this.pool.ensureRelay.bind(this.pool)
@@ -234,6 +233,7 @@ class ClientService extends EventTarget {
...params,
connectionTimeout
})
+ patchPoolRelayAuthRaceAndFeedback(relay)
applyRelayNip42AckTimeout(relay)
return relay
}