Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
7c1ecb673e
  1. 2
      package.json
  2. 4
      src/components/ProfileListBySearch/index.tsx
  3. 71
      src/components/SearchResult/FullTextSearchByRelay.tsx
  4. 35
      src/constants.ts
  5. 4
      src/hooks/useSearchProfiles.tsx
  6. 4
      src/lib/favorites-feed-relays.ts
  7. 12
      src/lib/relay-list-builder.ts
  8. 6
      src/lib/tombstone-events.ts
  9. 4
      src/lib/viewer-relay-defaults.ts
  10. 6
      src/pages/primary/SpellsPage/index.tsx
  11. 4
      src/pages/secondary/BookmarkListPage/index.tsx
  12. 7
      src/providers/NostrProvider/index.tsx
  13. 28
      src/services/client-replaceable-events.service.ts
  14. 38
      src/services/client.service.ts

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "imwald", "name": "imwald",
"version": "23.10.3", "version": "23.11.1",
"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",

4
src/components/ProfileListBySearch/index.tsx

@ -1,5 +1,5 @@
import { useSecondaryPage } from '@/PageManager' import { useSecondaryPage } from '@/PageManager'
import { PROFILE_FETCH_RELAY_URLS } from '@/constants' import { PROFILE_RELAY_URLS } from '@/constants'
import { decodeProfileSearchQueryToPubkeyHex } from '@/lib/profile-search-query' import { decodeProfileSearchQueryToPubkeyHex } from '@/lib/profile-search-query'
import { buildAlexandriaEventsSearchUrlForTSearchParams } from '@/lib/alexandria-events-search-url' import { buildAlexandriaEventsSearchUrlForTSearchParams } from '@/lib/alexandria-events-search-url'
import { toProfile } from '@/lib/link' import { toProfile } from '@/lib/link'
@ -15,7 +15,7 @@ import { AlexandriaEventsSearchEmptyCta } from '@/components/AlexandriaEventsSea
const LIMIT = 50 const LIMIT = 50
const PROFILE_SEARCH_RELAY_URLS = Array.from( const PROFILE_SEARCH_RELAY_URLS = Array.from(
new Set(PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)) new Set(PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean))
) )
export function ProfileListBySearch({ search }: { search: string }) { export function ProfileListBySearch({ search }: { search: string }) {

71
src/components/SearchResult/FullTextSearchByRelay.tsx

@ -266,8 +266,10 @@ export default function FullTextSearchByRelay({
relayRows.length > 0 && relayRows.every((r) => r.phase === 'done' || r.phase === 'error') relayRows.length > 0 && relayRows.every((r) => r.phase === 'done' || r.phase === 'error')
useEffect(() => { useEffect(() => {
const abort = new AbortController() /** Unmount / total wall only — must not abort in-flight NIP-50 when the “first hits + …ms” scheduling cutoff runs. */
const runAbort = new AbortController()
let masterTimer: ReturnType<typeof setTimeout> | null = null let masterTimer: ReturnType<typeof setTimeout> | null = null
let stopSchedulingTimer: ReturnType<typeof setTimeout> | null = null
const myRun = ++runGeneration.current const myRun = ++runGeneration.current
const cleanupInvalidatePreviousRun = () => { const cleanupInvalidatePreviousRun = () => {
runGeneration.current += 1 runGeneration.current += 1
@ -277,7 +279,11 @@ export default function FullTextSearchByRelay({
clearTimeout(masterTimer) clearTimeout(masterTimer)
masterTimer = null masterTimer = null
} }
abort.abort() if (stopSchedulingTimer != null) {
clearTimeout(stopSchedulingTimer)
stopSchedulingTimer = null
}
runAbort.abort()
cleanupInvalidatePreviousRun() cleanupInvalidatePreviousRun()
} }
@ -307,38 +313,44 @@ export default function FullTextSearchByRelay({
/** Set when the first {@link runOneRelay} begins (first real NIP-50 query); master wall clock starts then. */ /** Set when the first {@link runOneRelay} begins (first real NIP-50 query); master wall clock starts then. */
let waveT0: number | null = null let waveT0: number | null = null
let waveEndAt = 0 /** After first preview-visible relay hits: stop dequeuing new relays; in-flight REQs keep their per-relay budget. */
/** Only after ≥1 event from a relay: apply "first results + …ms" (empty EOSE must not shorten the wave). */ let stopSchedulingNewRelays = false
let appliedRelativeWaveCutoff = false /** Only after ≥1 preview-visible event from a relay: stop starting new relays after …ms (empty EOSE must not shorten). */
let appliedRelativeSchedulingCutoff = false
const scheduleMasterAbort = () => { const scheduleMasterWallAbort = () => {
if (masterTimer != null) { if (masterTimer != null) {
clearTimeout(masterTimer) clearTimeout(masterTimer)
masterTimer = null masterTimer = null
} }
const ms = Math.max(0, waveEndAt - Date.now()) if (waveT0 === null) return
const ms = Math.max(0, waveT0 + SEARCH_TOTAL_WALL_MS - Date.now())
masterTimer = setTimeout(() => { masterTimer = setTimeout(() => {
masterTimer = null masterTimer = null
abort.abort() stopSchedulingNewRelays = true
runAbort.abort()
}, ms) }, ms)
} }
const beginWaveIfNeeded = () => { const beginWaveIfNeeded = () => {
if (waveT0 !== null) return if (waveT0 !== null) return
waveT0 = Date.now() waveT0 = Date.now()
waveEndAt = waveT0 + SEARCH_TOTAL_WALL_MS scheduleMasterWallAbort()
scheduleMasterAbort()
} }
const onFirstSearchHits = () => { const onFirstPreviewVisibleRelayHits = () => {
if (appliedRelativeWaveCutoff || waveT0 === null) return if (appliedRelativeSchedulingCutoff || waveT0 === null) return
appliedRelativeWaveCutoff = true appliedRelativeSchedulingCutoff = true
const now = Date.now() if (stopSchedulingTimer != null) {
waveEndAt = Math.min(waveT0 + SEARCH_TOTAL_WALL_MS, now + SEARCH_AFTER_FIRST_RELAY_MS) clearTimeout(stopSchedulingTimer)
scheduleMasterAbort() }
stopSchedulingTimer = setTimeout(() => {
stopSchedulingTimer = null
stopSchedulingNewRelays = true
}, SEARCH_AFTER_FIRST_RELAY_MS)
} }
abort.signal.addEventListener( runAbort.signal.addEventListener(
'abort', 'abort',
() => { () => {
setRelayRows((prev) => setRelayRows((prev) =>
@ -415,7 +427,7 @@ export default function FullTextSearchByRelay({
includeOtherStoresFullText: true, includeOtherStoresFullText: true,
fullTextStoreHitCap: 260 fullTextStoreHitCap: 260
}) })
if (myRun !== runGeneration.current || abort.signal.aborted) return if (myRun !== runGeneration.current || runAbort.signal.aborted) return
const mergedLocalMatching = mergedLocal.filter((e) => mergedSearchNoteHasPreviewBody(e)) const mergedLocalMatching = mergedLocal.filter((e) => mergedSearchNoteHasPreviewBody(e))
if (mergedLocalMatching.length === 0) return if (mergedLocalMatching.length === 0) return
applyMergedUpdate((map) => { applyMergedUpdate((map) => {
@ -432,25 +444,24 @@ export default function FullTextSearchByRelay({
})() })()
const runOneRelay = async (relayUrl: string) => { const runOneRelay = async (relayUrl: string) => {
if (myRun !== runGeneration.current || abort.signal.aborted) return if (myRun !== runGeneration.current || runAbort.signal.aborted) return
beginWaveIfNeeded() beginWaveIfNeeded()
const t0 = performance.now() const t0 = performance.now()
const remainingWaveMs = Math.max(500, waveEndAt - Date.now())
const perRelayBudget = Math.min(SEARCH_PER_RELAY_QUERY_MS, remainingWaveMs)
try { try {
const { events: raw, connectionError } = await client.fetchEventsFromSingleRelay( const { events: raw, connectionError } = await client.fetchEventsFromSingleRelay(
relayUrl, relayUrl,
filter, filter,
{ globalTimeout: perRelayBudget, signal: abort.signal } { globalTimeout: SEARCH_PER_RELAY_QUERY_MS, signal: runAbort.signal }
) )
if (myRun !== runGeneration.current) return if (myRun !== runGeneration.current) return
const sorted = [...raw] const sorted = [...raw]
.sort((a, b) => compareEventsForDTagQuery(q, a, b)) .sort((a, b) => compareEventsForDTagQuery(q, a, b))
.slice(0, FULL_TEXT_SEARCH_MAX_NOTES_PER_RELAY) .slice(0, FULL_TEXT_SEARCH_MAX_NOTES_PER_RELAY)
const previewVisible = sorted.filter((e) => mergedSearchNoteHasPreviewBody(e))
const ms = Math.round(performance.now() - t0) const ms = Math.round(performance.now() - t0)
if (sorted.length === 0 && connectionError) { if (previewVisible.length === 0 && connectionError) {
setRelayRows((prev) => setRelayRows((prev) =>
prev.map((r) => prev.map((r) =>
r.relayUrl === relayUrl r.relayUrl === relayUrl
@ -462,10 +473,10 @@ export default function FullTextSearchByRelay({
} }
mergeIntoHits(relayUrl, sorted) mergeIntoHits(relayUrl, sorted)
void addSearchEventsToSessionCacheBatched(sorted, runGeneration, myRun) void addSearchEventsToSessionCacheBatched(previewVisible, runGeneration, myRun)
if (sorted.length > 0) { if (previewVisible.length > 0) {
onFirstSearchHits() onFirstPreviewVisibleRelayHits()
} }
setRelayRows((prev) => setRelayRows((prev) =>
prev.map((r) => prev.map((r) =>
@ -473,16 +484,16 @@ export default function FullTextSearchByRelay({
? { ? {
...r, ...r,
phase: 'done', phase: 'done',
eventCount: sorted.length, eventCount: previewVisible.length,
ms, ms,
errorMessage: sorted.length > 0 ? undefined : connectionError errorMessage: previewVisible.length > 0 ? undefined : connectionError
} }
: r : r
) )
) )
} catch (err) { } catch (err) {
if (myRun !== runGeneration.current) return if (myRun !== runGeneration.current) return
if (abort.signal.aborted) return if (runAbort.signal.aborted) return
const msg = err instanceof Error ? err.message : String(err) const msg = err instanceof Error ? err.message : String(err)
const ms = Math.round(performance.now() - t0) const ms = Math.round(performance.now() - t0)
setRelayRows((prev) => setRelayRows((prev) =>
@ -494,7 +505,7 @@ export default function FullTextSearchByRelay({
} }
const worker = async () => { const worker = async () => {
while (myRun === runGeneration.current && !abort.signal.aborted) { while (myRun === runGeneration.current && !runAbort.signal.aborted && !stopSchedulingNewRelays) {
const relayUrl = nextRelayUrl() const relayUrl = nextRelayUrl()
if (!relayUrl) break if (!relayUrl) break
await runOneRelay(relayUrl) await runOneRelay(relayUrl)

35
src/constants.ts

@ -73,9 +73,7 @@ export const DESKTOP_APP_DOWNLOAD_URL_DEFAULT =
export const DEFAULT_FAVORITE_RELAYS = [ export const DEFAULT_FAVORITE_RELAYS = [
'wss://theforest.nostr1.com', 'wss://theforest.nostr1.com',
'wss://christpill.nostr1.com', 'wss://nostr.land'
'wss://nostr.land',
'wss://nostr21.com'
] ]
/** /**
@ -284,7 +282,6 @@ export const PROFILE_FETCH_PROMISE_TIMEOUT_MS = 22_000
* @see https://blossom.happytavern.co/ — Lotus-style ephemeral Blossom (0x0 backend). * @see https://blossom.happytavern.co/ — Lotus-style ephemeral Blossom (0x0 backend).
*/ */
export const STANDARD_BLOSSOM_UPLOAD_HOSTS = [ export const STANDARD_BLOSSOM_UPLOAD_HOSTS = [
{ url: 'https://blossom.happytavern.co', labelKey: 'BlossomUploadOptionHappyTavern' },
{ url: 'https://0x0.happytavern.co', labelKey: 'BlossomUploadOptionHappyTavern' }, { url: 'https://0x0.happytavern.co', labelKey: 'BlossomUploadOptionHappyTavern' },
{ url: 'https://blossom.band', labelKey: 'BlossomUploadOptionBand' }, { url: 'https://blossom.band', labelKey: 'BlossomUploadOptionBand' },
{ url: 'https://blossom.primal.net', labelKey: 'BlossomUploadOptionPrimal' }, { url: 'https://blossom.primal.net', labelKey: 'BlossomUploadOptionPrimal' },
@ -415,7 +412,8 @@ export const READ_ONLY_RELAY_URLS = [
'wss://search.nos.today', 'wss://search.nos.today',
'wss://trending.nostr.wine', 'wss://trending.nostr.wine',
'wss://relay.nip46.com', 'wss://relay.nip46.com',
'wss://filter.nostr.wine' 'wss://filter.nostr.wine',
'wss://primus.nostr1.com'
] ]
/** /**
@ -455,14 +453,10 @@ export const E_TAG_FILTER_BLOCKED_RELAY_URLS = [
// Optimized relay list for read operations (includes aggregator) // Optimized relay list for read operations (includes aggregator)
export const FAST_READ_RELAY_URLS = [ export const FAST_READ_RELAY_URLS = [
'wss://theforest.nostr1.com', 'wss://theforest.nostr1.com',
'wss://orly-relay.imwald.eu',
'wss://nostr.wine',
'wss://nostr.land', 'wss://nostr.land',
'wss://nostr21.com', 'wss://nostr.wine',
'wss://thecitadel.nostr1.com', 'wss://orly-relay.imwald.eu',
'wss://aggr.nostr.land', 'wss://nostr21.com'
'wss://primus.nostr1.com',
'wss://wheat.happytavern.co'
] ]
// Optimized relay list for write operations (no aggregator since it's read-only) // Optimized relay list for write operations (no aggregator since it's read-only)
@ -477,11 +471,11 @@ export const FAST_WRITE_RELAY_URLS = [
/** Relays used for NIP-94 file metadata (kind 1063) / GIF discovery and publish. /** Relays used for NIP-94 file metadata (kind 1063) / GIF discovery and publish.
* Publish to all of these so GIFs are discoverable across clients; some may be temporarily down. */ * Publish to all of these so GIFs are discoverable across clients; some may be temporarily down. */
export const GIF_RELAY_URLS = [ export const GIF_RELAY_URLS = [
'wss://relay.gifbuddy.lol',
'wss://relay.damus.io', 'wss://relay.damus.io',
'wss://relay.primal.net', 'wss://relay.primal.net',
'wss://thecitadel.nostr1.com', 'wss://thecitadel.nostr1.com',
'wss://nos.lol', 'wss://nos.lol',
'wss://nostr.mom'
] ]
export const SEARCHABLE_RELAY_URLS = [ export const SEARCHABLE_RELAY_URLS = [
@ -489,29 +483,20 @@ export const SEARCHABLE_RELAY_URLS = [
'wss://nostr.wine', 'wss://nostr.wine',
'wss://orly-relay.imwald.eu', 'wss://orly-relay.imwald.eu',
'wss://relay.noswhere.com', 'wss://relay.noswhere.com',
'wss://relay.wikifreedia.xyz', 'wss://nostr-pub.wellorder.net'
'wss://nostr.einundzwanzig.space',
'wss://nostr-pub.wellorder.net',
'wss://pyramid.fiatjaf.com/',
'wss://nostrelites.org',
'wss://wheat.happytavern.co'
] ]
export const PROFILE_RELAY_URLS = [ export const PROFILE_RELAY_URLS = [
'wss://profiles.nostr1.com', 'wss://profiles.nostr1.com',
'wss://purplepag.es', 'wss://purplepag.es',
'wss://relay.primal.net', 'wss://profiles.nostrver.se/',
'wss://relay.damus.io', 'wss://indexer.coracle.social/'
'wss://nos.lol'
] ]
export const FOLLOWS_HISTORY_RELAY_URLS = [ export const FOLLOWS_HISTORY_RELAY_URLS = [
'wss://hist.nostr.land' 'wss://hist.nostr.land'
] ]
// Profile reads + NIP-50 profile search: search/index relays first, then fast read + profile mirrors (order preserved; dedupe at use sites).
export const PROFILE_FETCH_RELAY_URLS = [...SEARCHABLE_RELAY_URLS, ...FAST_READ_RELAY_URLS, ...PROFILE_RELAY_URLS]
export const ExtendedKind = { export const ExtendedKind = {
PICTURE: 20, PICTURE: 20,
VIDEO: 21, VIDEO: 21,

4
src/hooks/useSearchProfiles.tsx

@ -1,11 +1,11 @@
import { PROFILE_FETCH_RELAY_URLS } from '@/constants' import { PROFILE_RELAY_URLS } from '@/constants'
import { normalizeUrl } from '@/lib/url' import { normalizeUrl } from '@/lib/url'
import client from '@/services/client.service' import client from '@/services/client.service'
import { TProfile } from '@/types' import { TProfile } from '@/types'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
const PROFILE_SEARCH_RELAY_URLS = Array.from( const PROFILE_SEARCH_RELAY_URLS = Array.from(
new Set(PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)) new Set(PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean))
) )
export function useSearchProfiles(search: string, limit: number) { export function useSearchProfiles(search: string, limit: number) {

4
src/lib/favorites-feed-relays.ts

@ -2,7 +2,7 @@ import {
DEFAULT_FAVORITE_RELAYS, DEFAULT_FAVORITE_RELAYS,
DOCUMENT_RELAY_URLS, DOCUMENT_RELAY_URLS,
FAST_READ_RELAY_URLS, FAST_READ_RELAY_URLS,
PROFILE_FETCH_RELAY_URLS, PROFILE_RELAY_URLS,
READ_ONLY_RELAY_URLS, READ_ONLY_RELAY_URLS,
isDocumentRelayKind, isDocumentRelayKind,
relayFilterIncludesSocialKindBlockedKind relayFilterIncludesSocialKindBlockedKind
@ -222,7 +222,7 @@ export function buildProfilePageReadRelayUrls(
) )
/** Authors without kind 10002: widen REQ targets so notes/metadata are still discoverable on index relays. */ /** Authors without kind 10002: widen REQ targets so notes/metadata are still discoverable on index relays. */
if (authorHasNoNip65) { if (authorHasNoNip65) {
const profileSource = useGlobal ? PROFILE_FETCH_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer() const profileSource = useGlobal ? PROFILE_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer()
const profileFetchLayer = profileSource.map((u) => normalizeUrl(u) || u).filter(Boolean) as string[] const profileFetchLayer = profileSource.map((u) => normalizeUrl(u) || u).filter(Boolean) as string[]
return mergeRelayUrlLayers([urls, profileFetchLayer], blockedRelays).slice(0, maxRelays + 8) return mergeRelayUrlLayers([urls, profileFetchLayer], blockedRelays).slice(0, maxRelays + 8)
} }

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

@ -9,7 +9,7 @@
* - Includes seen relays * - Includes seen relays
*/ */
import { FAST_READ_RELAY_URLS, PROFILE_FETCH_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' import { FAST_READ_RELAY_URLS, PROFILE_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants'
import { feedRelayPolicyUrls } from '@/features/feed/relay-policy' import { feedRelayPolicyUrls } from '@/features/feed/relay-policy'
import { mergeRelayUrlLayers, userReadRelaysWithHttp } from '@/lib/favorites-feed-relays' import { mergeRelayUrlLayers, userReadRelaysWithHttp } from '@/lib/favorites-feed-relays'
import { urlIsNonLocalForRemoteViewer } from '@/lib/relay-list-sanitize' import { urlIsNonLocalForRemoteViewer } from '@/lib/relay-list-sanitize'
@ -38,7 +38,7 @@ function dedupeNormalizedRelayUrls(urls: string[]): string[] {
* PROFILE_FETCH + FAST_READ. * PROFILE_FETCH + FAST_READ.
*/ */
function exploreDiscoveryBootstrapRelayUrls(): string[] { function exploreDiscoveryBootstrapRelayUrls(): string[] {
return dedupeNormalizedRelayUrls([...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS]) return dedupeNormalizedRelayUrls([...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS])
} }
export interface RelayListBuilderOptions { export interface RelayListBuilderOptions {
@ -54,7 +54,7 @@ export interface RelayListBuilderOptions {
containingEventRelays?: string[] containingEventRelays?: string[]
/** Whether to include user's own relays (read/write/local) - for profiles/metadata */ /** Whether to include user's own relays (read/write/local) - for profiles/metadata */
includeUserOwnRelays?: boolean includeUserOwnRelays?: boolean
/** Whether to include PROFILE_FETCH_RELAY_URLS - for profiles/metadata */ /** Whether to include PROFILE_RELAY_URLS - for profiles/metadata */
includeProfileFetchRelays?: boolean includeProfileFetchRelays?: boolean
/** Whether to include FAST_READ_RELAY_URLS as fallback */ /** Whether to include FAST_READ_RELAY_URLS as fallback */
includeFastReadRelays?: boolean includeFastReadRelays?: boolean
@ -72,7 +72,7 @@ export interface RelayListBuilderOptions {
/** Whether to include user's favorite relays (kind 10012) */ /** Whether to include user's favorite relays (kind 10012) */
includeFavoriteRelays?: boolean includeFavoriteRelays?: boolean
/** /**
* When true with fast-read / searchable / profile-fetch includes: insert `PROFILE_FETCH_RELAY_URLS`, * When true with fast-read / searchable / profile-fetch includes: insert `PROFILE_RELAY_URLS`,
* `FAST_READ_RELAY_URLS`, and `SEARCHABLE_RELAY_URLS` immediately after hints/seen/containing and **before** * `FAST_READ_RELAY_URLS`, and `SEARCHABLE_RELAY_URLS` immediately after hints/seen/containing and **before**
* author + user NIP-65 lists. Used for batched metadata and embed fetches so public mirrors are not queued * author + user NIP-65 lists. Used for batched metadata and embed fetches so public mirrors are not queued
* behind broken personal relays under the global connection cap. * behind broken personal relays under the global connection cap.
@ -133,7 +133,7 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio
// connection slots on broken personal relays before PROFILE_FETCH + FAST_READ answer). // connection slots on broken personal relays before PROFILE_FETCH + FAST_READ answer).
if (preferPublicReadRelaysEarly) { if (preferPublicReadRelaysEarly) {
if (includeProfileFetchRelays) { if (includeProfileFetchRelays) {
PROFILE_FETCH_RELAY_URLS.forEach(addRelay) PROFILE_RELAY_URLS.forEach(addRelay)
} }
if (includeFastReadRelays) { if (includeFastReadRelays) {
FAST_READ_RELAY_URLS.forEach(addRelay) FAST_READ_RELAY_URLS.forEach(addRelay)
@ -212,7 +212,7 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio
// 6. Profile fetch relays (for profiles/metadata) // 6. Profile fetch relays (for profiles/metadata)
if (includeProfileFetchRelays) { if (includeProfileFetchRelays) {
PROFILE_FETCH_RELAY_URLS.forEach(addRelay) PROFILE_RELAY_URLS.forEach(addRelay)
} }
// 7. Fast read relays (fallback) // 7. Fast read relays (fallback)

6
src/lib/tombstone-events.ts

@ -1,4 +1,4 @@
import { PROFILE_FETCH_RELAY_URLS } from '@/constants' import { PROFILE_RELAY_URLS } from '@/constants'
import { normalizeAnyRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url' import { normalizeAnyRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url'
import type { TRelayList } from '@/types' import type { TRelayList } from '@/types'
@ -16,7 +16,7 @@ export function buildDeletionRelayUrls(relayList: TRelayList | null | undefined)
const httpW = relayList?.httpWrite ?? [] const httpW = relayList?.httpWrite ?? []
if (!relayList?.read?.length && !relayList?.write?.length && !httpR.length && !httpW.length) { if (!relayList?.read?.length && !relayList?.write?.length && !httpR.length && !httpW.length) {
return Array.from( return Array.from(
new Set(PROFILE_FETCH_RELAY_URLS.map((url) => normalizeUrl(url) || url).filter(Boolean)) new Set(PROFILE_RELAY_URLS.map((url) => normalizeUrl(url) || url).filter(Boolean))
).slice(0, 20) ).slice(0, 20)
} }
const ws = relayList?.write ?? [] const ws = relayList?.write ?? []
@ -27,7 +27,7 @@ export function buildDeletionRelayUrls(relayList: TRelayList | null | undefined)
...rs.slice(0, 8).map((url: string) => normalizeUrl(url) || url), ...rs.slice(0, 8).map((url: string) => normalizeUrl(url) || url),
...httpW.map((url: string) => normalizeHttpRelayUrl(url) || url), ...httpW.map((url: string) => normalizeHttpRelayUrl(url) || url),
...httpR.slice(0, 8).map((url: string) => normalizeHttpRelayUrl(url) || url), ...httpR.slice(0, 8).map((url: string) => normalizeHttpRelayUrl(url) || url),
...PROFILE_FETCH_RELAY_URLS.map((url: string) => normalizeAnyRelayUrl(url) || url) ...PROFILE_RELAY_URLS.map((url: string) => normalizeAnyRelayUrl(url) || url)
]) ])
).slice(0, 20) ).slice(0, 20)
} }

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

@ -1,7 +1,7 @@
import { import {
DEFAULT_FAVORITE_RELAYS, DEFAULT_FAVORITE_RELAYS,
FAST_READ_RELAY_URLS, FAST_READ_RELAY_URLS,
PROFILE_FETCH_RELAY_URLS PROFILE_RELAY_URLS
} from '@/constants' } from '@/constants'
import { normalizeUrl } from '@/lib/url' import { normalizeUrl } from '@/lib/url'
@ -43,7 +43,7 @@ const fastReadKeySet = (): Set<string> => {
/** PROFILE_FETCH stack with {@link FAST_READ_RELAY_URLS} entries removed (order preserved). */ /** PROFILE_FETCH stack with {@link FAST_READ_RELAY_URLS} entries removed (order preserved). */
export function profileFetchRelayUrlsWithoutFastReadLayer(): string[] { export function profileFetchRelayUrlsWithoutFastReadLayer(): string[] {
const drop = fastReadKeySet() const drop = fastReadKeySet()
return PROFILE_FETCH_RELAY_URLS.filter((u) => { return PROFILE_RELAY_URLS.filter((u) => {
const n = (normalizeUrl(u) || u).toLowerCase() const n = (normalizeUrl(u) || u).toLowerCase()
return n && !drop.has(n) return n && !drop.has(n)
}) })

6
src/pages/primary/SpellsPage/index.tsx

@ -27,7 +27,7 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider' import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider'
import { useBookmarks } from '@/providers/bookmarks-context' import { useBookmarks } from '@/providers/bookmarks-context'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { useNotificationThreadWatch } from '@/providers/NotificationThreadWatchProvider' import { useNotificationThreadWatchOptional } from '@/providers/NotificationThreadWatchProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useUserTrust } from '@/contexts/user-trust-context' import { useUserTrust } from '@/contexts/user-trust-context'
import { dedupeFollowSetEventsByD } from '@/lib/follow-set-spell' import { dedupeFollowSetEventsByD } from '@/lib/follow-set-spell'
@ -89,7 +89,9 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
} = useNostr() } = useNostr()
const { addBookmark, removeBookmark } = useBookmarks() const { addBookmark, removeBookmark } = useBookmarks()
const { hideUntrustedNotifications } = useUserTrust() const { hideUntrustedNotifications } = useUserTrust()
const { eventsIFollowListEvent, eventsIMutedListEvent } = useNotificationThreadWatch() const notificationThreadWatch = useNotificationThreadWatchOptional()
const eventsIFollowListEvent = notificationThreadWatch?.eventsIFollowListEvent ?? null
const eventsIMutedListEvent = notificationThreadWatch?.eventsIMutedListEvent ?? null
const { isSmallScreen } = useScreenSize() const { isSmallScreen } = useScreenSize()
const { favoriteRelays, blockedRelays } = useFavoriteRelays() const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const useGlobalRelayBootstrap = useGlobalRelayBootstrapDefaults() const useGlobalRelayBootstrap = useGlobalRelayBootstrapDefaults()

4
src/pages/secondary/BookmarkListPage/index.tsx

@ -27,7 +27,7 @@ import { getLatestEvent } from '@/lib/event'
import { buildAccountListRelayUrlsForMerge } from '@/lib/account-list-relay-urls' import { buildAccountListRelayUrlsForMerge } from '@/lib/account-list-relay-urls'
import { fetchLatestReplaceableListEvent } from '@/lib/replaceable-list-latest' import { fetchLatestReplaceableListEvent } from '@/lib/replaceable-list-latest'
import { normalizeUrl } from '@/lib/url' import { normalizeUrl } from '@/lib/url'
import { PROFILE_FETCH_RELAY_URLS } from '@/constants' import { PROFILE_RELAY_URLS } from '@/constants'
import { queryService } from '@/services/client.service' import { queryService } from '@/services/client.service'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Code, Eraser, MoreVertical } from 'lucide-react' import { Code, Eraser, MoreVertical } from 'lucide-react'
@ -64,7 +64,7 @@ const BookmarkListPage = forwardRef(
const urls = Array.from( const urls = Array.from(
new Set( new Set(
[ [
...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u),
...(relayList?.write ?? []).map((u) => normalizeUrl(u) || u) ...(relayList?.write ?? []).map((u) => normalizeUrl(u) || u)
].filter(Boolean) ].filter(Boolean)
) )

7
src/providers/NostrProvider/index.tsx

@ -7,7 +7,6 @@ import {
DEFAULT_FAVORITE_RELAYS, DEFAULT_FAVORITE_RELAYS,
FAST_READ_RELAY_URLS, FAST_READ_RELAY_URLS,
ExtendedKind, ExtendedKind,
PROFILE_FETCH_RELAY_URLS,
PROFILE_RELAY_URLS, PROFILE_RELAY_URLS,
SEARCHABLE_RELAY_URLS, SEARCHABLE_RELAY_URLS,
UNSIGNED_EXPERIMENTAL_KIND_MAX, UNSIGNED_EXPERIMENTAL_KIND_MAX,
@ -543,7 +542,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
...mergedRelayList.write.map((url: string) => normalizeUrl(url) || url), ...mergedRelayList.write.map((url: string) => normalizeUrl(url) || url),
...mergedRelayList.read.map((url: string) => normalizeUrl(url) || url), ...mergedRelayList.read.map((url: string) => normalizeUrl(url) || url),
...FAST_READ_RELAY_URLS.map((url: string) => normalizeUrl(url) || url), ...FAST_READ_RELAY_URLS.map((url: string) => normalizeUrl(url) || url),
...PROFILE_FETCH_RELAY_URLS.map((url: string) => normalizeUrl(url) || url) ...PROFILE_RELAY_URLS.map((url: string) => normalizeUrl(url) || url)
] ]
const fetchRelays = Array.from(new Set(normalizedRelays)).slice(0, 16) const fetchRelays = Array.from(new Set(normalizedRelays)).slice(0, 16)
const events = await queryService.fetchEvents(fetchRelays, [ const events = await queryService.fetchEvents(fetchRelays, [
@ -706,7 +705,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
...mergedRelayList.write.map((u) => normalizeUrl(u) || u), ...mergedRelayList.write.map((u) => normalizeUrl(u) || u),
...mergedRelayList.read.map((u) => normalizeUrl(u) || u), ...mergedRelayList.read.map((u) => normalizeUrl(u) || u),
...SEARCHABLE_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...SEARCHABLE_RELAY_URLS.map((u) => normalizeUrl(u) || u),
...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u),
...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u) ...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u)
]) ])
).filter(Boolean) ).filter(Boolean)
@ -914,7 +913,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
...rl.write.map((u) => normalizeUrl(u) || u), ...rl.write.map((u) => normalizeUrl(u) || u),
...rl.read.map((u) => normalizeUrl(u) || u), ...rl.read.map((u) => normalizeUrl(u) || u),
...SEARCHABLE_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...SEARCHABLE_RELAY_URLS.map((u) => normalizeUrl(u) || u),
...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u),
...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u) ...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u)
]) ])
).filter(Boolean) ).filter(Boolean)

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

@ -7,7 +7,7 @@ import {
METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS, METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS,
METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS, METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS,
PROFILE_BATCH_NETWORK_LOAD_TIMEOUT_MS, PROFILE_BATCH_NETWORK_LOAD_TIMEOUT_MS,
PROFILE_FETCH_RELAY_URLS, PROFILE_RELAY_URLS,
READ_ONLY_RELAY_URLS, READ_ONLY_RELAY_URLS,
RECOMMENDED_BLOSSOM_SERVERS RECOMMENDED_BLOSSOM_SERVERS
} from '@/constants' } from '@/constants'
@ -120,7 +120,7 @@ export class ReplaceableEventService {
/** /**
* Build comprehensive relay list: author's outboxes + user's inboxes + relay hints + defaults * Build comprehensive relay list: author's outboxes + user's inboxes + relay hints + defaults
* For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_FETCH_RELAY_URLS * For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_RELAY_URLS
*/ */
private async buildComprehensiveRelayListForAuthor( private async buildComprehensiveRelayListForAuthor(
authorPubkey: string, authorPubkey: string,
@ -138,7 +138,7 @@ export class ReplaceableEventService {
relayHints, relayHints,
containingEventRelays, containingEventRelays,
includeUserOwnRelays: isProfileOrMetadata, // For profiles/metadata, include user's own relays includeUserOwnRelays: isProfileOrMetadata, // For profiles/metadata, include user's own relays
includeProfileFetchRelays: isProfileOrMetadata, // For profiles/metadata, include PROFILE_FETCH_RELAY_URLS includeProfileFetchRelays: isProfileOrMetadata, // For profiles/metadata, include PROFILE_RELAY_URLS
includeFastReadRelays: true, includeFastReadRelays: true,
includeLocalRelays: true includeLocalRelays: true
}) })
@ -148,7 +148,7 @@ export class ReplaceableEventService {
* Fetch replaceable event (profile, relay list, etc.) * Fetch replaceable event (profile, relay list, etc.)
* Uses DataLoader to batch IndexedDB checks and network fetches * Uses DataLoader to batch IndexedDB checks and network fetches
* ALWAYS uses: author's outboxes + user's inboxes + relay hints + defaults * ALWAYS uses: author's outboxes + user's inboxes + relay hints + defaults
* For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_FETCH_RELAY_URLS * For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_RELAY_URLS
* *
* @param pubkey - Author's pubkey * @param pubkey - Author's pubkey
* @param kind - Event kind * @param kind - Event kind
@ -518,11 +518,11 @@ export class ReplaceableEventService {
Array.from(missingGroups.entries()).map(async ([kind, missingItems]) => { Array.from(missingGroups.entries()).map(async ([kind, missingItems]) => {
const pubkeys = missingItems.map(item => item.pubkey) const pubkeys = missingItems.map(item => item.pubkey)
// ALWAYS use comprehensive relay list: author's outboxes + user's inboxes + defaults // ALWAYS use comprehensive relay list: author's outboxes + user's inboxes + defaults
// For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_FETCH_RELAY_URLS // For profiles/metadata: includes user's own relays (read/write/local) + PROFILE_RELAY_URLS
// For each pubkey, build comprehensive relay list // For each pubkey, build comprehensive relay list
// CRITICAL FIX: For batch fetches, use default relays instead of fetching relay lists for each author // CRITICAL FIX: For batch fetches, use default relays instead of fetching relay lists for each author
// Fetching relay lists for hundreds of authors causes infinite loops and browser crashes // Fetching relay lists for hundreds of authors causes infinite loops and browser crashes
// Use PROFILE_FETCH_RELAY_URLS + FAST_READ_RELAY_URLS for profiles, or FAST_READ_RELAY_URLS for other kinds. // Use PROFILE_RELAY_URLS + FAST_READ_RELAY_URLS for profiles, or FAST_READ_RELAY_URLS for other kinds.
// For metadata with a logged-in user, merge defaults with {@link buildComprehensiveRelayList}: inboxes (read), // For metadata with a logged-in user, merge defaults with {@link buildComprehensiveRelayList}: inboxes (read),
// local/cache relays (10432), favorite relays (10012), plus profile + fast read — same idea as favorites feed // local/cache relays (10432), favorite relays (10012), plus profile + fast read — same idea as favorites feed
// / inbox-scoped discovery without per-author relay list fetches. // / inbox-scoped discovery without per-author relay list fetches.
@ -546,10 +546,10 @@ export class ReplaceableEventService {
preferPublicReadRelaysEarly: true preferPublicReadRelaysEarly: true
}) })
} catch { } catch {
relayUrls = Array.from(new Set([...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS])) relayUrls = Array.from(new Set([...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS]))
} }
} else { } else {
relayUrls = Array.from(new Set([...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS])) relayUrls = Array.from(new Set([...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS]))
} }
} else if (kind === ExtendedKind.FAVORITE_RELAYS) { } else if (kind === ExtendedKind.FAVORITE_RELAYS) {
relayUrls = await buildExploreProfileAndUserRelayList(client.pubkey) relayUrls = await buildExploreProfileAndUserRelayList(client.pubkey)
@ -558,7 +558,7 @@ export class ReplaceableEventService {
// and 100ms EOSE loses the race when several relays are down. // and 100ms EOSE loses the race when several relays are down.
relayUrls = Array.from( relayUrls = Array.from(
new Set( new Set(
[...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map(
(u) => normalizeUrl(u) || u (u) => normalizeUrl(u) || u
) )
) )
@ -567,7 +567,7 @@ export class ReplaceableEventService {
// Contacts (kind 3): aggregators + profile mirrors + fast read. // Contacts (kind 3): aggregators + profile mirrors + fast read.
relayUrls = Array.from( relayUrls = Array.from(
new Set( new Set(
[...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map(
(u) => normalizeUrl(u) || u (u) => normalizeUrl(u) || u
) )
) )
@ -576,7 +576,7 @@ export class ReplaceableEventService {
// NIP-65 (10002): aggregators + profile mirrors + fast read. // NIP-65 (10002): aggregators + profile mirrors + fast read.
relayUrls = Array.from( relayUrls = Array.from(
new Set( new Set(
[...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map(
(u) => normalizeUrl(u) || u (u) => normalizeUrl(u) || u
) )
) )
@ -585,7 +585,7 @@ export class ReplaceableEventService {
// Mute / bookmark lists: same distribution as contacts; FAST_READ + mirrors. // Mute / bookmark lists: same distribution as contacts; FAST_READ + mirrors.
relayUrls = Array.from( relayUrls = Array.from(
new Set( new Set(
[...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map(
(u) => normalizeUrl(u) || u (u) => normalizeUrl(u) || u
) )
) )
@ -594,7 +594,7 @@ export class ReplaceableEventService {
// NIP-A3 kind 10133: aggregators + profile mirrors + fast read. // NIP-A3 kind 10133: aggregators + profile mirrors + fast read.
relayUrls = Array.from( relayUrls = Array.from(
new Set( new Set(
[...READ_ONLY_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS].map( [...READ_ONLY_RELAY_URLS, ...PROFILE_RELAY_URLS, ...FAST_READ_RELAY_URLS].map(
(u) => normalizeUrl(u) || u (u) => normalizeUrl(u) || u
) )
) )
@ -937,7 +937,7 @@ export class ReplaceableEventService {
new Set([ new Set([
...relayHints, ...relayHints,
...authorRelays, ...authorRelays,
...PROFILE_FETCH_RELAY_URLS, ...PROFILE_RELAY_URLS,
...FAST_READ_RELAY_URLS ...FAST_READ_RELAY_URLS
]) ])
) )

38
src/services/client.service.ts

@ -29,7 +29,7 @@ import {
EARLY_PUBLISH_SUCCESS_GRACE_MS, EARLY_PUBLISH_SUCCESS_GRACE_MS,
DEFAULT_FAVORITE_RELAYS, DEFAULT_FAVORITE_RELAYS,
NIP66_DISCOVERY_RELAY_URLS, NIP66_DISCOVERY_RELAY_URLS,
PROFILE_FETCH_RELAY_URLS, PROFILE_RELAY_URLS,
PROFILE_RELAY_URLS, PROFILE_RELAY_URLS,
READ_ONLY_RELAY_URLS, READ_ONLY_RELAY_URLS,
NIP42_POOL_AUTOMATIC_AUTH_RELAY_URLS, NIP42_POOL_AUTOMATIC_AUTH_RELAY_URLS,
@ -1242,12 +1242,12 @@ class ClientService extends EventTarget {
].includes(event.kind) ].includes(event.kind)
) { ) {
bootstrapExtras.push( bootstrapExtras.push(
...(useGlobalRelayDefaults ? PROFILE_FETCH_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer()) ...(useGlobalRelayDefaults ? PROFILE_RELAY_URLS : profileFetchRelayUrlsWithoutFastReadLayer())
) )
logger.debug('[DetermineTargetRelays] Relay list event detected, adding PROFILE_FETCH_RELAY_URLS', { logger.debug('[DetermineTargetRelays] Relay list event detected, adding PROFILE_RELAY_URLS', {
kind: event.kind, kind: event.kind,
profileFetchRelays: useGlobalRelayDefaults profileFetchRelays: useGlobalRelayDefaults
? PROFILE_FETCH_RELAY_URLS ? PROFILE_RELAY_URLS
: profileFetchRelayUrlsWithoutFastReadLayer(), : profileFetchRelayUrlsWithoutFastReadLayer(),
additionalRelayCount: bootstrapExtras.length additionalRelayCount: bootstrapExtras.length
}) })
@ -1264,7 +1264,7 @@ class ClientService extends EventTarget {
}) })
} else if (event.kind === ExtendedKind.RSS_FEED_LIST) { } else if (event.kind === ExtendedKind.RSS_FEED_LIST) {
if (useGlobalRelayDefaults) { if (useGlobalRelayDefaults) {
bootstrapExtras.push(...FAST_WRITE_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS) bootstrapExtras.push(...FAST_WRITE_RELAY_URLS, ...PROFILE_RELAY_URLS)
} else { } else {
bootstrapExtras.push(...profileFetchRelayUrlsWithoutFastReadLayer()) bootstrapExtras.push(...profileFetchRelayUrlsWithoutFastReadLayer())
} }
@ -3733,7 +3733,7 @@ class ClientService extends EventTarget {
/** /**
* Npubs for @-mention dropdown: (1) follow-list profiles matching the query, * Npubs for @-mention dropdown: (1) follow-list profiles matching the query,
* (2) local index, (3) kind-0 NIP-50 search on {@link PROFILE_FETCH_RELAY_URLS} (includes search relays + profile mirrors; deduped). * (2) local index, (3) kind-0 NIP-50 search on {@link PROFILE_RELAY_URLS} (includes search relays + profile mirrors; deduped).
* Returns cached results immediately, then streams relay results via callback. * Returns cached results immediately, then streams relay results via callback.
*/ */
/** /**
@ -3775,7 +3775,7 @@ class ClientService extends EventTarget {
const pk = authorPubkey?.trim().toLowerCase() const pk = authorPubkey?.trim().toLowerCase()
if (!pk || !/^[0-9a-f]{64}$/.test(pk)) return if (!pk || !/^[0-9a-f]{64}$/.test(pk)) return
const urls = (relayUrls.length > 0 ? relayUrls : [...PROFILE_FETCH_RELAY_URLS]) const urls = (relayUrls.length > 0 ? relayUrls : [...PROFILE_RELAY_URLS])
.map((u) => normalizeUrl(u) || u) .map((u) => normalizeUrl(u) || u)
.filter(Boolean) .filter(Boolean)
const capped = Array.from(new Set(urls)).slice(0, 16) const capped = Array.from(new Set(urls)).slice(0, 16)
@ -3867,7 +3867,7 @@ class ClientService extends EventTarget {
// Relay query starts immediately so it can run in parallel with local + follow work (slow relays). // Relay query starts immediately so it can run in parallel with local + follow work (slow relays).
const profileSearchRelayUrls = dedupeNormalizeRelayUrlsOrdered( const profileSearchRelayUrls = dedupeNormalizeRelayUrlsOrdered(
PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean) PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)
) )
const relayTask = const relayTask =
q.length >= 1 q.length >= 1
@ -4152,8 +4152,8 @@ class ClientService extends EventTarget {
const [fallback] = await this.mergeRelayListsFromStoredOnly([pubkey]) const [fallback] = await this.mergeRelayListsFromStoredOnly([pubkey])
return fallback! return fallback!
} catch { } catch {
const read = PROFILE_FETCH_RELAY_URLS const read = PROFILE_RELAY_URLS
const write = PROFILE_FETCH_RELAY_URLS const write = PROFILE_RELAY_URLS
return { return {
write, write,
read, read,
@ -4174,7 +4174,7 @@ class ClientService extends EventTarget {
/** /**
* Merge relay list from IndexedDB only (no network). Same rules as a timed-out {@link fetchRelayLists}: * Merge relay list from IndexedDB only (no network). Same rules as a timed-out {@link fetchRelayLists}:
* defaults to {@link PROFILE_FETCH_RELAY_URLS} when kind 10002 is missing. * defaults to {@link PROFILE_RELAY_URLS} when kind 10002 is missing.
*/ */
async peekRelayListFromStorage(pubkey: string): Promise<TRelayList> { async peekRelayListFromStorage(pubkey: string): Promise<TRelayList> {
const [rl] = await this.mergeRelayListsFromStoredOnly([pubkey]) const [rl] = await this.mergeRelayListsFromStoredOnly([pubkey])
@ -4308,14 +4308,14 @@ class ClientService extends EventTarget {
if (isOwnRelayList && storedCacheEvent) { if (isOwnRelayList && storedCacheEvent) {
const cacheRelayList = getRelayListFromEvent(storedCacheEvent) const cacheRelayList = getRelayListFromEvent(storedCacheEvent)
return mergeKind10243({ return mergeKind10243({
write: cacheRelayList.write.length > 0 ? cacheRelayList.write : PROFILE_FETCH_RELAY_URLS, write: cacheRelayList.write.length > 0 ? cacheRelayList.write : PROFILE_RELAY_URLS,
read: cacheRelayList.read.length > 0 ? cacheRelayList.read : PROFILE_FETCH_RELAY_URLS, read: cacheRelayList.read.length > 0 ? cacheRelayList.read : PROFILE_RELAY_URLS,
originalRelays: cacheRelayList.originalRelays, originalRelays: cacheRelayList.originalRelays,
...emptyHttp ...emptyHttp
}) })
} }
let read = PROFILE_FETCH_RELAY_URLS let read = PROFILE_RELAY_URLS
let write = PROFILE_FETCH_RELAY_URLS let write = PROFILE_RELAY_URLS
if (!isOwnRelayList) { if (!isOwnRelayList) {
const stripped = stripMailboxLocalUrlsForRemoteViewers({ read, write }) const stripped = stripMailboxLocalUrlsForRemoteViewers({ read, write })
read = read =
@ -4551,8 +4551,8 @@ class ClientService extends EventTarget {
try { try {
return await this.mergeRelayListsFromStoredOnly(pubkeys) return await this.mergeRelayListsFromStoredOnly(pubkeys)
} catch { } catch {
const read = PROFILE_FETCH_RELAY_URLS const read = PROFILE_RELAY_URLS
const write = PROFILE_FETCH_RELAY_URLS const write = PROFILE_RELAY_URLS
return pubkeys.map(() => ({ return pubkeys.map(() => ({
write, write,
read, read,
@ -4571,7 +4571,7 @@ class ClientService extends EventTarget {
/** /**
* Fetch cache relay events (kind 10432) from multiple sources: * Fetch cache relay events (kind 10432) from multiple sources:
* - PROFILE_FETCH_RELAY_URLS * - PROFILE_RELAY_URLS
* - User's inboxes (read relays from kind 10002) * - User's inboxes (read relays from kind 10002)
* - User's outboxes (write relays from kind 10002) * - User's outboxes (write relays from kind 10002)
*/ */
@ -4708,7 +4708,7 @@ class ClientService extends EventTarget {
...relayList.write.map((u) => normalizeUrl(u) || u), ...relayList.write.map((u) => normalizeUrl(u) || u),
...relayList.read.map((u) => normalizeUrl(u) || u), ...relayList.read.map((u) => normalizeUrl(u) || u),
...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u), ...FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u),
...PROFILE_FETCH_RELAY_URLS.map((u) => normalizeUrl(u) || u) ...PROFILE_RELAY_URLS.map((u) => normalizeUrl(u) || u)
]).filter(Boolean) ]).filter(Boolean)
const capped = urls.slice(0, 20) const capped = urls.slice(0, 20)
if (capped.length === 0) return [] if (capped.length === 0) return []

Loading…
Cancel
Save