Browse Source

fix relay connections

imwald
Silberengel 3 weeks ago
parent
commit
cb275a28d9
  1. 2
      package.json
  2. 3
      src/components/ConnectedRelays/ActiveRelaysTitlebarButton.tsx
  3. 3
      src/components/ConnectedRelays/ConnectedRelaysSidebarStrip.tsx
  4. 2
      src/components/MetadataRelaysOnlySetting/index.tsx
  5. 109
      src/hooks/useRelayConnectionRows.ts
  6. 4
      src/i18n/locales/de.ts
  7. 4
      src/i18n/locales/en.ts
  8. 8
      src/lib/read-only-relay-personal.test.ts
  9. 15
      src/lib/read-only-relay-personal.ts
  10. 6
      src/lib/viewer-relay-defaults.ts
  11. 8
      src/services/client-query.service.ts
  12. 13
      src/services/client.service.ts

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "imwald", "name": "imwald",
"version": "23.14.0", "version": "23.15.0",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery", "description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true, "private": true,
"type": "module", "type": "module",

3
src/components/ConnectedRelays/ActiveRelaysTitlebarButton.tsx

@ -30,8 +30,7 @@ function rowTitle(url: string, connected: boolean, t: (k: string) => string) {
} }
/** /**
* Same interaction pattern as {@link SeenOnButton}: Server + counts, menu lists relays with {@link RelayIcon}. * Server icon + menu listing relays with an open WebSocket in the pool.
* Shows favorites + default/inbox relays; disconnected relays are muted.
*/ */
export function ActiveRelaysTitlebarButton() { export function ActiveRelaysTitlebarButton() {
const { t } = useTranslation() const { t } = useTranslation()

3
src/components/ConnectedRelays/ConnectedRelaysSidebarStrip.tsx

@ -32,8 +32,7 @@ function rowTitle(url: string, connected: boolean, t: (k: string) => string) {
} }
/** /**
* Desktop sidebar: relay avatars for favorites, inbox, cache, HTTP index, and defaults; * Desktop sidebar: relay avatars for relays with an open WebSocket in the pool.
* muted when the WebSocket is down (HTTP index relays count as active when configured).
*/ */
export function ConnectedRelaysSidebarStrip({ className }: { className?: string }) { export function ConnectedRelaysSidebarStrip({ className }: { className?: string }) {
const { t } = useTranslation() const { t } = useTranslation()

2
src/components/MetadataRelaysOnlySetting/index.tsx

@ -31,7 +31,7 @@ export default function MetadataRelaysOnlySetting() {
</div> </div>
<div className="text-muted-foreground text-xs max-w-xl"> <div className="text-muted-foreground text-xs max-w-xl">
{t( {t(
'When on, the app only connects to relays on your Read & Write, Favorite, Cache, and HTTP relay lists. It will not open background connections to public mirrors, author outboxes, or other suggested relays.' 'When on, the app only opens read connections to relays on your Read & Write, Favorite, Cache, and HTTP relay lists (plus profile and search index relays). Publishing is unchanged. Relay explore and Search pages are exempt.'
)} )}
</div> </div>
</div> </div>

109
src/hooks/useRelayConnectionRows.ts

@ -1,16 +1,4 @@
import { DEFAULT_FAVORITE_RELAYS, FAST_READ_RELAY_URLS } from '@/constants' import { canonicalRelaySessionKey, normalizeAnyRelayUrl, normalizeHttpRelayUrl } from '@/lib/url'
import {
getHttpRelayListFromEvent,
getRelayListReadFromEventNoFastFallback
} from '@/lib/event-metadata'
import {
canonicalRelaySessionKey,
normalizeAnyRelayUrl,
normalizeHttpRelayUrl,
urlMatchesConfiguredHttpIndexRelay
} from '@/lib/url'
import { useNostr } from '@/providers/NostrProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import client from '@/services/client.service' import client from '@/services/client.service'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
@ -26,107 +14,38 @@ function rowCanon(url: string): string {
return (canonicalRelaySessionKey(url) || normalizeRelayRowUrl(url)).trim().toLowerCase() return (canonicalRelaySessionKey(url) || normalizeRelayRowUrl(url)).trim().toLowerCase()
} }
function mergeUniquePreserveOrder(...lists: (readonly string[] | undefined)[]): string[] {
const seen = new Set<string>()
const out: string[] = []
for (const list of lists) {
if (!list?.length) continue
for (const raw of list) {
const n = normalizeRelayRowUrl(raw)
const k = rowCanon(n)
if (!k || seen.has(k)) continue
seen.add(k)
out.push(n)
}
}
return out
}
export type TRelayConnectionRow = { export type TRelayConnectionRow = {
url: string url: string
/** WebSocket open in the pool, or HTTP index relay in use for the viewer. */ /** WebSocket open in the pool. */
connected: boolean connected: boolean
} }
/** /**
* Relays for active relays UI: favorites + NIP-65 read/write + kind 10432 cache + kind 10243 HTTP index * Relays for active relays UI: only relays with an open WebSocket in the pool right now.
* + defaults + fast-read, then any pool-connected URL not already listed.
*/ */
export function useRelayConnectionRows(): { export function useRelayConnectionRows(): {
rows: TRelayConnectionRow[] rows: TRelayConnectionRow[]
/** Relays counted as active (open WebSocket or configured HTTP index). */
connectedCount: number connectedCount: number
} { } {
const { relayList, cacheRelayListEvent, httpRelayListEvent } = useNostr() const [connectedUrls, setConnectedUrls] = useState<string[]>(() => client.getConnectedRelayUrls())
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const [connectedCanon, setConnectedCanon] = useState<Set<string>>(() =>
new Set(client.getConnectedRelayUrls().map(rowCanon))
)
const [httpIndexBases, setHttpIndexBases] = useState<readonly string[]>(() =>
client.getViewerHttpIndexRelayBases()
)
useEffect(() => { useEffect(() => {
const tick = () => { const tick = () => setConnectedUrls(client.getConnectedRelayUrls())
setConnectedCanon(new Set(client.getConnectedRelayUrls().map(rowCanon)))
setHttpIndexBases(client.getViewerHttpIndexRelayBases())
}
tick() tick()
const id = window.setInterval(tick, POLL_MS) const id = window.setInterval(tick, POLL_MS)
return () => clearInterval(id) return () => clearInterval(id)
}, []) }, [])
const cacheRelayUrls = useMemo(() => {
if (!cacheRelayListEvent) return []
return getRelayListReadFromEventNoFastFallback(cacheRelayListEvent, blockedRelays)
}, [cacheRelayListEvent, blockedRelays])
const httpIndexRelayUrls = useMemo(() => {
const out: string[] = [...(relayList?.httpRead ?? []), ...(relayList?.httpWrite ?? [])]
if (httpRelayListEvent) {
const http = getHttpRelayListFromEvent(httpRelayListEvent, blockedRelays)
out.push(...http.httpRead, ...http.httpWrite)
}
return out
}, [relayList?.httpRead, relayList?.httpWrite, httpRelayListEvent, blockedRelays])
return useMemo(() => { return useMemo(() => {
const inbox = [...(relayList?.read ?? []), ...(relayList?.write ?? [])] const seen = new Set<string>()
const base = mergeUniquePreserveOrder( const rows: TRelayConnectionRow[] = []
favoriteRelays, for (const raw of connectedUrls) {
inbox, const url = normalizeRelayRowUrl(raw)
cacheRelayUrls,
httpIndexRelayUrls,
DEFAULT_FAVORITE_RELAYS,
FAST_READ_RELAY_URLS
)
const baseCanon = new Set(base.map(rowCanon))
const isConnected = (url: string) =>
urlMatchesConfiguredHttpIndexRelay(url, httpIndexBases) || connectedCanon.has(rowCanon(url))
const rowFor = (url: string): TRelayConnectionRow => ({
url,
connected: isConnected(url)
})
const rows: TRelayConnectionRow[] = base.map((url) => rowFor(url))
for (const url of client.getConnectedRelayUrls()) {
const k = rowCanon(url) const k = rowCanon(url)
if (baseCanon.has(k)) continue if (!k || seen.has(k)) continue
rows.push(rowFor(url)) seen.add(k)
rows.push({ url, connected: true })
} }
return { rows, connectedCount: rows.length }
const connectedCount = rows.filter((r) => r.connected).length }, [connectedUrls])
return { rows, connectedCount }
}, [
favoriteRelays,
relayList?.read,
relayList?.write,
cacheRelayUrls,
httpIndexRelayUrls,
connectedCanon,
httpIndexBases
])
} }

4
src/i18n/locales/de.ts

@ -108,8 +108,8 @@ export default {
"Relay Settings": "Relay-Einstellungen", "Relay Settings": "Relay-Einstellungen",
"Relays and Storage Settings": "Relays und Speicher", "Relays and Storage Settings": "Relays und Speicher",
"Only my relay lists": "Nur meine Relay-Listen", "Only my relay lists": "Nur meine Relay-Listen",
"When on, the app only connects to relays on your Read & Write, Favorite, Cache, and HTTP relay lists. It will not open background connections to public mirrors, author outboxes, or other suggested relays.": "When on, the app only opens read connections to relays on your Read & Write, Favorite, Cache, and HTTP relay lists (plus profile and search index relays). Publishing is unchanged. Relay explore and Search pages are exempt.":
"Wenn aktiv, werden Feeds nicht mehr auf generische öffentliche Leserelays (FAST_READ) oder zufällige Autoren-/Hinweis-Relays erweitert. Deine Relay-Listen, Profil- und Suchindex-Relays, Dokument-Relays und aggr.nostr.land (mit Nostr Land) bleiben aktiv. Relay-Entdecken und Suche sind ausgenommen.", "Wenn aktiv, werden nur noch Lese-Verbindungen zu Relays auf deinen Listen (plus Profil- und Suchindex-Relays) geöffnet. Veröffentlichen bleibt unverändert. Relay-Entdecken und Suche sind ausgenommen.",
"Relay set name": "Relay-Set Name", "Relay set name": "Relay-Set Name",
"Add a new relay set": "Neues Relay-Set hinzufügen", "Add a new relay set": "Neues Relay-Set hinzufügen",
Add: "Hinzufügen", Add: "Hinzufügen",

4
src/i18n/locales/en.ts

@ -113,8 +113,8 @@ export default {
"Relay Settings": "Relays and Storage Settings", "Relay Settings": "Relays and Storage Settings",
"Relays and Storage Settings": "Relays and Storage Settings", "Relays and Storage Settings": "Relays and Storage Settings",
"Only my relay lists": "Only my relay lists", "Only my relay lists": "Only my relay lists",
"When on, the app only connects to relays on your Read & Write, Favorite, Cache, and HTTP relay lists. It will not open background connections to public mirrors, author outboxes, or other suggested relays.": "When on, the app only opens read connections to relays on your Read & Write, Favorite, Cache, and HTTP relay lists (plus profile and search index relays). Publishing is unchanged. Relay explore and Search pages are exempt.":
"When on, the app stops widening feeds to generic public read relays (FAST_READ) and random author or hint relays. Your relay lists, profile and search index relays, document relays, and aggr.nostr.land (with Nostr Land) still work. Relay explore and Search pages are exempt.", "When on, the app stops widening feeds to generic public read relays (FAST_READ) and random author or hint relays. Your relay lists, profile and search index relays, document relays, and aggr.nostr.land (with Nostr Land) still work. Publishing is unchanged. Relay explore and Search pages are exempt.",
"Relay set name": "Relay set name", "Relay set name": "Relay set name",
"Add a new relay set": "Add a new relay set", "Add a new relay set": "Add a new relay set",
Add: "Add", Add: "Add",

8
src/lib/read-only-relay-personal.test.ts

@ -73,7 +73,7 @@ describe('read-only-relay-personal', () => {
expect(filterReadOnlyRelaysUnlessPersonal(urls)).toEqual(urls) expect(filterReadOnlyRelaysUnlessPersonal(urls)).toEqual(urls)
}) })
it('metadata-only policy blocks ad-hoc and FAST_READ bootstrap relays, keeps profile relays', () => { it('metadata-only policy blocks ad-hoc reads at network level, not in sanitizeRelayUrlsForFetch', () => {
setRestrictConnectionsToMetadataRelaysOnly(true) setRestrictConnectionsToMetadataRelaysOnly(true)
setViewerPersonalRelayKeys(buildPersonalRelayKeySet(['wss://relay.example.com/']), { viewerActive: true }) setViewerPersonalRelayKeys(buildPersonalRelayKeySet(['wss://relay.example.com/']), { viewerActive: true })
const urls = [ const urls = [
@ -82,10 +82,7 @@ describe('read-only-relay-personal', () => {
'wss://theforest.nostr1.com/', 'wss://theforest.nostr1.com/',
'wss://nostr.wirednet.jp/' 'wss://nostr.wirednet.jp/'
] ]
expect(sanitizeRelayUrlsForFetch(urls)).toEqual([ expect(sanitizeRelayUrlsForFetch(urls)).toEqual(urls)
'wss://relay.example.com/',
'wss://profiles.nostr1.com/'
])
expect(isRelayConnectionAllowedForViewer('wss://profiles.nostr1.com/')).toBe(true) expect(isRelayConnectionAllowedForViewer('wss://profiles.nostr1.com/')).toBe(true)
expect(isRelayConnectionAllowedForViewer('wss://theforest.nostr1.com/')).toBe(false) expect(isRelayConnectionAllowedForViewer('wss://theforest.nostr1.com/')).toBe(false)
expect(isRelayConnectionAllowedForViewer('wss://nostr.wirednet.jp/')).toBe(false) expect(isRelayConnectionAllowedForViewer('wss://nostr.wirednet.jp/')).toBe(false)
@ -99,6 +96,7 @@ describe('read-only-relay-personal', () => {
expect(sanitizeRelayUrlsForFetch(urls).map((u) => u.replace(/\/$/, ''))).toEqual([ expect(sanitizeRelayUrlsForFetch(urls).map((u) => u.replace(/\/$/, ''))).toEqual([
'wss://nostr.land', 'wss://nostr.land',
'wss://aggr.nostr.land', 'wss://aggr.nostr.land',
'wss://nostr.wirednet.jp'
]) ])
expect(isRelayConnectionAllowedForViewer(AGGR_NOSTR_LAND_WSS)).toBe(true) expect(isRelayConnectionAllowedForViewer(AGGR_NOSTR_LAND_WSS)).toBe(true)
expect(isRelayConnectionAllowedForViewer('wss://nostr.wirednet.jp/')).toBe(false) expect(isRelayConnectionAllowedForViewer('wss://nostr.wirednet.jp/')).toBe(false)

15
src/lib/read-only-relay-personal.ts

@ -95,17 +95,12 @@ export function isRelayAllowedUnderMetadataOnlyPolicy(url: string): boolean {
return false return false
} }
/** Block WebSocket (and other) pool connects when metadata-only policy is on. */ /** Block read-side pool connects / HTTP index fetches when metadata-only policy is on. */
export function isRelayConnectionAllowedForViewer(url: string): boolean { export function isRelayConnectionAllowedForViewer(url: string): boolean {
if (!isMetadataRelaysOnlyPolicyActive()) return true if (!isMetadataRelaysOnlyPolicyActive()) return true
return isRelayAllowedUnderMetadataOnlyPolicy(url) return isRelayAllowedUnderMetadataOnlyPolicy(url)
} }
function filterToViewerMetadataRelaysOnly(urls: readonly string[]): string[] {
if (!isMetadataRelaysOnlyPolicyActive()) return [...urls]
return urls.filter((u) => isRelayAllowedUnderMetadataOnlyPolicy(u))
}
export function relayUrlKey(url: string): string { export function relayUrlKey(url: string): string {
return (normalizeAnyRelayUrl(url) || url.trim()).toLowerCase() return (normalizeAnyRelayUrl(url) || url.trim()).toLowerCase()
} }
@ -183,11 +178,9 @@ export function sanitizeRelayUrlsForFetch(
const key = relayUrlKey(u) const key = relayUrlKey(u)
return key.length > 0 && keys.has(key) return key.length > 0 && keys.has(key)
}) })
return filterToViewerMetadataRelaysOnly( return filterViewerBlockedRelaysForFetch(
filterViewerBlockedRelaysForFetch( filterAggrNostrLandUnlessViewerEligible(
filterAggrNostrLandUnlessViewerEligible( filterReadOnlyRelaysUnlessPersonal(withoutThirdPartyLocals, keys)
filterReadOnlyRelaysUnlessPersonal(withoutThirdPartyLocals, keys)
)
) )
) )
} }

6
src/lib/viewer-relay-defaults.ts

@ -3,7 +3,6 @@ import {
FAST_READ_RELAY_URLS, FAST_READ_RELAY_URLS,
PROFILE_RELAY_URLS PROFILE_RELAY_URLS
} from '@/constants' } from '@/constants'
import { isMetadataRelaysOnlyPolicyActive } from '@/lib/read-only-relay-personal'
import { normalizeUrl } from '@/lib/url' import { normalizeUrl } from '@/lib/url'
export type ViewerRelayListLike = { export type ViewerRelayListLike = {
@ -17,9 +16,9 @@ export type ViewerRelayListLike = {
* the user is not signed in, or when they are signed in but have configured neither favorite relays nor a NIP-65 * the user is not signed in, or when they are signed in but have configured neither favorite relays nor a NIP-65
* (kind 10002 / HTTP index) relay list. Otherwise REQ/publish stacks should stay on their own relays. * (kind 10002 / HTTP index) relay list. Otherwise REQ/publish stacks should stay on their own relays.
*/ */
/** Public read mirrors used when relay lists are empty; empty when metadata-only policy is on. */ /** Public read mirrors used when relay lists are empty. */
export function publicReadRelayFallbackUrls(): readonly string[] { export function publicReadRelayFallbackUrls(): readonly string[] {
return isMetadataRelaysOnlyPolicyActive() ? [] : FAST_READ_RELAY_URLS return FAST_READ_RELAY_URLS
} }
export function viewerUsesGlobalRelayDefaults(args: { export function viewerUsesGlobalRelayDefaults(args: {
@ -27,7 +26,6 @@ export function viewerUsesGlobalRelayDefaults(args: {
favoriteRelayUrls: readonly string[] favoriteRelayUrls: readonly string[]
relayList: ViewerRelayListLike relayList: ViewerRelayListLike
}): boolean { }): boolean {
if (isMetadataRelaysOnlyPolicyActive()) return false
if (!args.viewerPubkey?.trim()) return true if (!args.viewerPubkey?.trim()) return true
const hasFavorites = args.favoriteRelayUrls.some((u) => typeof u === 'string' && u.trim().length > 0) const hasFavorites = args.favoriteRelayUrls.some((u) => typeof u === 'string' && u.trim().length > 0)
const rl = args.relayList const rl = args.relayList

8
src/services/client-query.service.ts

@ -40,7 +40,7 @@ import { patchRelayNoticeForFetchFailures } from '@/services/relay-notice-fetch-
import type { Filter, Event as NEvent } from 'nostr-tools' import type { Filter, Event as NEvent } from 'nostr-tools'
import { SimplePool, EventTemplate, VerifiedEvent, nip19 } from 'nostr-tools' import { SimplePool, EventTemplate, VerifiedEvent, nip19 } from 'nostr-tools'
import type { AbstractRelay } from 'nostr-tools/abstract-relay' import type { AbstractRelay } from 'nostr-tools/abstract-relay'
import { sanitizeRelayUrlsForFetch } from '@/lib/read-only-relay-personal' import { sanitizeRelayUrlsForFetch, isRelayConnectionAllowedForViewer } from '@/lib/read-only-relay-personal'
import { publicReadRelayFallbackUrls } from '@/lib/viewer-relay-defaults' import { publicReadRelayFallbackUrls } from '@/lib/viewer-relay-defaults'
import nip66Service from './nip66.service' import nip66Service from './nip66.service'
import type { ISigner, TSignerType } from '@/types' import type { ISigner, TSignerType } from '@/types'
@ -479,9 +479,9 @@ export class QueryService {
? FIRST_RELAY_RESULT_GRACE_MS ? FIRST_RELAY_RESULT_GRACE_MS
: null : null
const httpRelayBases = httpIndexBasesForRelayQuery(urls, options?.httpIndexRelayBases ?? []).filter( const httpRelayBases = httpIndexBasesForRelayQuery(urls, options?.httpIndexRelayBases ?? [])
(u) => !relaySessionStrikes.isReadHttpSkipped(u) .filter((u) => !relaySessionStrikes.isReadHttpSkipped(u))
) .filter((u) => isRelayConnectionAllowedForViewer(u))
const httpKeys = new Set(httpRelayBases.map((u) => canonicalRelaySessionKey(u))) const httpKeys = new Set(httpRelayBases.map((u) => canonicalRelaySessionKey(u)))
const wsQueryUrls = urls.filter((u) => !httpKeys.has(canonicalRelaySessionKey(u))) const wsQueryUrls = urls.filter((u) => !httpKeys.has(canonicalRelaySessionKey(u)))

13
src/services/client.service.ts

@ -42,7 +42,6 @@ import {
isReadOnlyIndexerRelay, isReadOnlyIndexerRelay,
isReadOnlyRelayAllowedForViewer, isReadOnlyRelayAllowedForViewer,
isRelayConnectionAllowedForViewer, isRelayConnectionAllowedForViewer,
isMetadataRelaysOnlyPolicyActive,
setViewerPersonalRelayKeys setViewerPersonalRelayKeys
} from '@/lib/read-only-relay-personal' } from '@/lib/read-only-relay-personal'
import { import {
@ -438,7 +437,7 @@ class ClientService extends EventTarget {
const rawEnsureRelay = this.pool.ensureRelay.bind(this.pool) const rawEnsureRelay = this.pool.ensureRelay.bind(this.pool)
this.pool.ensureRelay = async ( this.pool.ensureRelay = async (
url: string, url: string,
params?: { connectionTimeout?: number; abort?: AbortSignal } params?: { connectionTimeout?: number; abort?: AbortSignal; purpose?: 'read' | 'write' }
) => { ) => {
// While offline, skip any relay that isn't on the local network. // While offline, skip any relay that isn't on the local network.
// This prevents a flood of failed WebSocket/HTTP connection attempts across // This prevents a flood of failed WebSocket/HTTP connection attempts across
@ -446,7 +445,7 @@ class ClientService extends EventTarget {
if (!navigator.onLine && !isLocalNetworkUrl(url)) { if (!navigator.onLine && !isLocalNetworkUrl(url)) {
throw new Error(`[offline] skipping non-local relay ${url}`) throw new Error(`[offline] skipping non-local relay ${url}`)
} }
if (!isRelayConnectionAllowedForViewer(url)) { if (params?.purpose !== 'write' && !isRelayConnectionAllowedForViewer(url)) {
throw new Error(`[metadata-relays-only] skipping relay ${url}`) throw new Error(`[metadata-relays-only] skipping relay ${url}`)
} }
if (!isWebsocketUrl(url) && isKind10243HttpRelayTagUrl(url)) { if (!isWebsocketUrl(url) && isKind10243HttpRelayTagUrl(url)) {
@ -1772,7 +1771,7 @@ class ClientService extends EventTarget {
wsAttempt wsAttempt
}) })
const ensureOpts = { connectionTimeout } const ensureOpts = { connectionTimeout, purpose: 'write' as const }
const connectionPromise = isLocal const connectionPromise = isLocal
? Promise.race([ ? Promise.race([
this.pool.ensureRelay(url, ensureOpts), this.pool.ensureRelay(url, ensureOpts),
@ -4522,7 +4521,7 @@ class ClientService extends EventTarget {
stripped.write.length > 0 ? stripped.write : write.filter(urlIsNonLocalForRemoteViewer) stripped.write.length > 0 ? stripped.write : write.filter(urlIsNonLocalForRemoteViewer)
if (read.length === 0 && write.length === 0) { if (read.length === 0 && write.length === 0) {
read = [...publicReadRelayFallbackUrls()] read = [...publicReadRelayFallbackUrls()]
write = isMetadataRelaysOnlyPolicyActive() ? [] : [...FAST_WRITE_RELAY_URLS] write = [...FAST_WRITE_RELAY_URLS]
} }
} }
return mergeKind10243({ return mergeKind10243({
@ -4961,9 +4960,7 @@ class ClientService extends EventTarget {
let urls = [...publicReadRelayFallbackUrls()] let urls = [...publicReadRelayFallbackUrls()]
if (myPubkey) { if (myPubkey) {
const relayList = await this.fetchRelayList(myPubkey) const relayList = await this.fetchRelayList(myPubkey)
urls = isMetadataRelaysOnlyPolicyActive() urls = relayList.read.concat([...publicReadRelayFallbackUrls()]).slice(0, 5)
? relayList.read.slice(0, 5)
: relayList.read.concat([...publicReadRelayFallbackUrls()]).slice(0, 5)
} }
return [{ urls, filter: { authors: pubkeys } }] return [{ urls, filter: { authors: pubkeys } }]
} }

Loading…
Cancel
Save