From ed2ca2a7e97c5ebf95a2d7867e616df3d24f9397 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 6 Apr 2026 19:47:11 +0200 Subject: [PATCH] quiet the console and fix relay list --- .../PostEditor/PostRelaySelector.tsx | 151 +++-------------- src/lib/logger.ts | 156 ++++++++---------- src/services/relay-selection.service.ts | 71 +++----- 3 files changed, 112 insertions(+), 266 deletions(-) diff --git a/src/components/PostEditor/PostRelaySelector.tsx b/src/components/PostEditor/PostRelaySelector.tsx index ed34720e..2c1af151 100644 --- a/src/components/PostEditor/PostRelaySelector.tsx +++ b/src/components/PostEditor/PostRelaySelector.tsx @@ -17,7 +17,7 @@ import { userReadRelaysWithHttp } from '@/lib/favorites-feed-relays' import indexedDb from '@/services/indexed-db.service' import { Check, ChevronDown, Server } from 'lucide-react' import { NostrEvent } from 'nostr-tools' -import { Dispatch, SetStateAction, useCallback, useEffect, useState, useMemo } from 'react' +import { Dispatch, SetStateAction, useCallback, useEffect, useState, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import RelayIcon from '../RelayIcon' import relaySelectionService, { type RelaySourceType } from '@/services/relay-selection.service' @@ -69,12 +69,9 @@ export default function PostRelaySelector({ const [isLoading, setIsLoading] = useState(true) const [hasManualSelection, setHasManualSelection] = useState(false) const [previousSelectableCount, setPreviousSelectableCount] = useState(0) - const [previousMentions, setPreviousMentions] = useState([]) - - // Initialize previousMentions with the initial mentions value - useEffect(() => { - setPreviousMentions(mentions) - }, []) // Only run once on mount + // Generation counter: incremented every time the effect fires; async callback checks whether + // it's still the latest invocation before committing state, preventing stale races. + const selectionGenRef = useRef(0) // For discussion replies, content doesn't affect relay selection // Check if this is a reply to a discussion by looking for "K" tag with "11" @@ -181,28 +178,26 @@ export default function PostRelaySelector({ const memoizedRelaySets = useMemo(() => relaySets, [relaySets]) const memoizedOpenFrom = useMemo(() => openFrom, [openFrom]) - // Use centralized relay selection service - only for non-content dependencies + // Single relay-selection effect. The generation counter (selectionGenRef) guards against + // stale async completions: if a newer invocation has started, the older one discards its results. useEffect(() => { + const gen = ++selectionGenRef.current + const updateRelaySelection = async () => { setIsLoading(true) try { - // Ensure cache relays (kind 10432) are included in userWriteRelays even if relayList hasn't been updated yet - // Get cache relays directly from IndexedDB (don't fetch new every time) let userWriteRelays = relayList?.write || [] if (pubkey) { try { const cacheRelayListEvent = await indexedDb.getReplaceableEvent(pubkey, ExtendedKind.CACHE_RELAYS) if (cacheRelayListEvent) { const cacheRelayList = getRelayListFromEvent(cacheRelayListEvent) - // Get all cache relays (they should all be local network URLs) - // Include both write and both-scoped relays (cache relays should be write-capable) const cacheRelays = [ ...cacheRelayList.write, ...cacheRelayList.originalRelays .filter(relay => (relay.scope === 'both' || relay.scope === 'write') && isLocalNetworkUrl(relay.url)) .map(relay => relay.url) ].filter(url => { - // Filter out invalid/empty URLs if (!url || typeof url !== 'string' || url.trim() === '' || url === 'ws://' || url === 'wss://') return false return isLocalNetworkUrl(url) }) @@ -218,7 +213,7 @@ export default function PostRelaySelector({ logger.warn('Failed to get cache relays from IndexedDB', { error, pubkey }) } } - + const result = await relaySelectionService.selectRelays({ userWriteRelays, userHttpWriteRelays: relayList?.httpWrite ?? [], @@ -228,43 +223,41 @@ export default function PostRelaySelector({ relaySets: memoizedRelaySets, parentEvent: _parentEvent, isPublicMessage, - content: isDiscussionReply ? '' : postContent, // Don't use content for discussion replies - mentions: isPublicMessage ? mentions : undefined, // Pass mentions for PMs + content: isDiscussionReply ? '' : postContent, + mentions: isPublicMessage ? mentions : undefined, userPubkey: pubkey || undefined, openFrom: memoizedOpenFrom }) + // Discard results from a superseded invocation + if (gen !== selectionGenRef.current) return + const newSelectableCount = result.selectableRelays.length const selectableRelaysChanged = newSelectableCount !== previousSelectableCount - + setSelectableRelays(result.selectableRelays) setRelayTypes(result.relayTypes ?? {}) setPreviousSelectableCount(newSelectableCount) - - // Only update selected relays if: - // 1. User hasn't manually modified them, OR - // 2. Selectable relays changed + if (!hasManualSelection || selectableRelaysChanged) { - // Ensure cache relays are included by default (but user can uncheck them) const cacheRelays = result.selectableRelays.filter(url => isLocalNetworkUrl(url)) const selectedWithCache = Array.from(new Set([...result.selectedRelays, ...cacheRelays])) setSelectedRelayUrls(selectedWithCache) setDescription(describeRelaySelection(selectedWithCache)) - // Reset manual selection flag if relays changed if (selectableRelaysChanged && hasManualSelection) { setHasManualSelection(false) } } - - } catch (error) { - logger.error('Failed to update relay selection', { error }) + } catch (error) { + if (gen !== selectionGenRef.current) return + logger.error('Failed to update relay selection', { error }) setSelectableRelays([]) if (!hasManualSelection) { setSelectedRelayUrls([]) setDescription(t('No relays selected')) } } finally { - setIsLoading(false) + if (gen === selectionGenRef.current) setIsLoading(false) } } @@ -285,110 +278,6 @@ export default function PostRelaySelector({ t ]) - // Separate effect for mention changes in non-discussion replies - useEffect(() => { - if (isDiscussionReply) return // Skip for discussion replies - - const mentionsChanged = JSON.stringify(mentions) !== JSON.stringify(previousMentions) - - if (mentionsChanged) { - setPreviousMentions(mentions) - - // Update relay selection when mentions change - const updateRelaySelection = async () => { - setIsLoading(true) - try { - // Ensure cache relays (kind 10432) are included in userWriteRelays even if relayList hasn't been updated yet - // Get cache relays directly from IndexedDB (don't fetch new every time) - let userWriteRelays = relayList?.write || [] - if (pubkey) { - try { - const cacheRelayListEvent = await indexedDb.getReplaceableEvent(pubkey, ExtendedKind.CACHE_RELAYS) - if (cacheRelayListEvent) { - const cacheRelayList = getRelayListFromEvent(cacheRelayListEvent) - // Get all cache relays (they should all be local network URLs) - // Include both write and both-scoped relays (cache relays should be write-capable) - const cacheRelays = [ - ...cacheRelayList.write, - ...cacheRelayList.originalRelays - .filter(relay => (relay.scope === 'both' || relay.scope === 'write') && isLocalNetworkUrl(relay.url)) - .map(relay => relay.url) - ].filter(url => isLocalNetworkUrl(url)) - const existingUrls = new Set(userWriteRelays.map(url => normalizeUrl(url) || url)) - const newCacheRelays = cacheRelays - .map(url => normalizeUrl(url) || url) - .filter((url): url is string => !!url && !existingUrls.has(url)) - if (newCacheRelays.length > 0) { - userWriteRelays = [...newCacheRelays, ...userWriteRelays] - } - } - } catch (error) { - logger.warn('Failed to get cache relays from IndexedDB', { error, pubkey }) - } - } - - const result = await relaySelectionService.selectRelays({ - userWriteRelays, - userHttpWriteRelays: relayList?.httpWrite ?? [], - userReadRelays: userReadRelaysForSelection, - favoriteRelays: memoizedFavoriteRelays, - blockedRelays: memoizedBlockedRelays, - relaySets: memoizedRelaySets, - parentEvent: _parentEvent, - isPublicMessage, - content: isDiscussionReply ? '' : postContent, // Don't use content for discussion replies - mentions: isPublicMessage ? mentions : undefined, // Pass mentions for PMs - userPubkey: pubkey || undefined, - openFrom: memoizedOpenFrom - }) - - const newSelectableCount = result.selectableRelays.length - const selectableRelaysChanged = newSelectableCount !== previousSelectableCount - - setSelectableRelays(result.selectableRelays) - setRelayTypes(result.relayTypes ?? {}) - setPreviousSelectableCount(newSelectableCount) - - // Only update selected relays if: - // 1. User hasn't manually modified them, OR - // 2. Selectable relays changed - if (!hasManualSelection || selectableRelaysChanged) { - // Ensure cache relays are included by default (but user can uncheck them) - const cacheRelays = result.selectableRelays.filter(url => isLocalNetworkUrl(url)) - const selectedWithCache = Array.from(new Set([...result.selectedRelays, ...cacheRelays])) - setSelectedRelayUrls(selectedWithCache) - setDescription(describeRelaySelection(selectedWithCache)) - // Reset manual selection flag if relays changed - if (selectableRelaysChanged && hasManualSelection) { - setHasManualSelection(false) - } - } - - } catch (error) { - logger.error('Failed to update relay selection', { error }) - } finally { - setIsLoading(false) - } - } - - updateRelaySelection() - } - }, [ - mentions, - isDiscussionReply, - memoizedFavoriteRelays, - memoizedBlockedRelays, - memoizedRelaySets, - _parentEvent, - isPublicMessage, - pubkey, - relayList, - memoizedOpenFrom, - previousSelectableCount, - hasManualSelection, - describeRelaySelection - ]) - // Update description when selected relays change due to manual selection useEffect(() => { if (hasManualSelection && !isLoading) { diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 4b5fecdf..68de2b52 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -1,149 +1,127 @@ /** - * Centralized logging utility to reduce console noise and improve performance - * - * Usage: - * - Use logger.debug() for development debugging (only shows in dev mode) - * - Use logger.info() for important information (always shows) - * - Use logger.warn() for warnings (always shows) - * - Use logger.error() for errors (always shows) - * - * In production builds, debug logs are completely removed to improve performance. + * Centralized logging utility. + * + * Level matrix: + * dev + debug flag → debug / info / warn / error (full formatted output) + * dev (no flag) → info / warn / error (formatted, no stack) + * production → warn / error only (bare console — no timestamp string built) + * + * Enable debug in dev: localStorage.setItem('jumble-debug', 'true') then reload. */ type LogLevel = 'debug' | 'info' | 'warn' | 'error' -interface LoggerConfig { - level: LogLevel - enableDebug: boolean - enablePerformance: boolean -} +const LEVELS: LogLevel[] = ['debug', 'info', 'warn', 'error'] class Logger { - private config: LoggerConfig + private readonly isDev = import.meta.env.DEV + private enableDebug: boolean + private minLevel: LogLevel constructor() { - // In production, disable debug logging for better performance - const isDev = import.meta.env.DEV - const isDebugEnabled = - isDev && + this.enableDebug = + this.isDev && (localStorage.getItem('imwald-debug') === 'true' || localStorage.getItem('jumble-debug') === 'true' || import.meta.env.VITE_DEBUG === 'true') - - this.config = { - level: isDebugEnabled ? 'debug' : 'info', - enableDebug: isDebugEnabled, - enablePerformance: isDev - } + + // In production only warn/error reach the console — info is noise for end-users. + this.minLevel = this.enableDebug ? 'debug' : this.isDev ? 'info' : 'warn' } private shouldLog(level: LogLevel): boolean { - const levels = ['debug', 'info', 'warn', 'error'] - const currentLevelIndex = levels.indexOf(this.config.level) - const messageLevelIndex = levels.indexOf(level) - return messageLevelIndex >= currentLevelIndex + return LEVELS.indexOf(level) >= LEVELS.indexOf(this.minLevel) } private getCallerInfo(): string { const stack = new Error().stack if (!stack) return 'unknown' - - const lines = stack.split('\n') - // Skip the first 3 lines (Error, getCallerInfo, formatMessage) - // Look for the first line that contains a file path - for (let i = 3; i < lines.length; i++) { - const line = lines[i] - const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/) - if (match) { - const [, functionName, filePath] = match - const fileName = filePath.split('/').pop()?.replace('.tsx', '').replace('.ts', '') || 'unknown' - return `${fileName}:${functionName}` + for (const line of stack.split('\n').slice(3)) { + const m = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/) + if (m) { + const fileName = m[2].split('/').pop()?.replace(/\.[tj]sx?$/, '') ?? 'unknown' + return `${fileName}:${m[1]}` } } return 'unknown' } - private formatMessage(level: LogLevel, message: string, ...args: any[]): [string, ...any[]] { - const timestamp = new Date().toISOString().substring(11, 23) // HH:mm:ss.SSS - // Stack capture is expensive (main-thread jank, especially on mobile). Only when deep debug is on. - const caller = this.config.enableDebug ? this.getCallerInfo() : '' - const prefix = caller - ? `[${timestamp}] [${level.toUpperCase()}] [${caller}]` - : `[${timestamp}] [${level.toUpperCase()}]` - return [`${prefix} ${message}`, ...args] + private prefix(level: LogLevel): string { + const ts = new Date().toISOString().substring(11, 23) + const caller = this.enableDebug ? ` [${this.getCallerInfo()}]` : '' + return `[${ts}] [${level.toUpperCase()}]${caller}` } - debug(message: string, ...args: any[]): void { - if (!this.config.enableDebug || !this.shouldLog('debug')) return - console.log(...this.formatMessage('debug', message, ...args)) + debug(message: string, ...args: unknown[]): void { + if (!this.enableDebug) return + console.log(`${this.prefix('debug')} ${message}`, ...args) } - info(message: string, ...args: any[]): void { + info(message: string, ...args: unknown[]): void { if (!this.shouldLog('info')) return - console.log(...this.formatMessage('info', message, ...args)) + // In production this branch is never reached (minLevel === 'warn'). + console.log(`${this.prefix('info')} ${message}`, ...args) } - warn(message: string, ...args: any[]): void { + warn(message: string, ...args: unknown[]): void { if (!this.shouldLog('warn')) return - console.warn(...this.formatMessage('warn', message, ...args)) + if (this.isDev) { + console.warn(`${this.prefix('warn')} ${message}`, ...args) + } else { + // In production: no string-building overhead; browser devtools add their own timestamp. + console.warn(message, ...args) + } } - error(message: string, ...args: any[]): void { + error(message: string, ...args: unknown[]): void { if (!this.shouldLog('error')) return - console.error(...this.formatMessage('error', message, ...args)) + if (this.isDev) { + console.error(`${this.prefix('error')} ${message}`, ...args) + } else { + console.error(message, ...args) + } } - // Performance logging for development - perf(message: string, ...args: any[]): void { - if (!this.config.enablePerformance) return + /** Dev-only performance marker. */ + perf(message: string, ...args: unknown[]): void { + if (!this.isDev) return console.log(`[PERF] ${message}`, ...args) } - // Group logging for related operations + /** Run `fn` inside a console group (debug mode only). */ group(label: string, fn: () => void): void { - if (!this.config.enableDebug) { - fn() - return - } + if (!this.enableDebug) { fn(); return } console.group(label) fn() console.groupEnd() } - // Conditional logging based on environment - dev(message: string, ...args: any[]): void { - if (import.meta.env.DEV) { - console.log(message, ...args) - } + /** Raw dev-only log — no formatting. */ + dev(message: string, ...args: unknown[]): void { + if (this.isDev) console.log(message, ...args) } - // Enable/disable debug mode at runtime setDebugMode(enabled: boolean): void { - this.config.enableDebug = enabled - this.config.level = enabled ? 'debug' : 'info' - localStorage.setItem('imwald-debug', enabled.toString()) - localStorage.setItem('jumble-debug', enabled.toString()) + this.enableDebug = enabled + this.minLevel = enabled ? 'debug' : this.isDev ? 'info' : 'warn' + localStorage.setItem('imwald-debug', String(enabled)) + localStorage.setItem('jumble-debug', String(enabled)) } - // Check if debug mode is enabled isDebugEnabled(): boolean { - return this.config.enableDebug + return this.enableDebug } - // Context-aware logging for components - component(componentName: string, message: string, ...args: any[]): void { - if (!this.config.enableDebug) return - const timestamp = new Date().toISOString().substring(11, 23) - const caller = this.getCallerInfo() - console.log(`[${timestamp}] [COMPONENT] [${componentName}] [${caller}] ${message}`, ...args) + /** Component-scoped debug log (debug mode only). */ + component(componentName: string, message: string, ...args: unknown[]): void { + if (!this.enableDebug) return + console.log(`${this.prefix('debug')} [${componentName}] ${message}`, ...args) } - // Performance logging with context - perfComponent(componentName: string, operation: string, ...args: any[]): void { - if (!this.config.enablePerformance) return - const timestamp = new Date().toISOString().substring(11, 23) - const caller = this.getCallerInfo() - console.log(`[${timestamp}] [PERF] [${componentName}] [${caller}] ${operation}`, ...args) + /** Component-scoped perf log (dev only). */ + perfComponent(componentName: string, operation: string, ...args: unknown[]): void { + if (!this.isDev) return + console.log(`[PERF] [${componentName}] ${operation}`, ...args) } } diff --git a/src/services/relay-selection.service.ts b/src/services/relay-selection.service.ts index e7eca92a..aff9ea31 100644 --- a/src/services/relay-selection.service.ts +++ b/src/services/relay-selection.service.ts @@ -3,7 +3,7 @@ import { ExtendedKind, FAST_WRITE_RELAY_URLS, RANDOM_PUBLISH_RELAY_COUNT } from import { NOSTR_URI_FOR_REPLY_PUBKEYS_REGEX } from '@/lib/content-patterns' import client from '@/services/client.service' import { eventService } from '@/services/client.service' -import { normalizeAnyRelayUrl, normalizeUrl, isLocalNetworkUrl } from '@/lib/url' +import { normalizeAnyRelayUrl, isLocalNetworkUrl } from '@/lib/url' import { TRelaySet, TRelayList } from '@/types' import logger from '@/lib/logger' import indexedDb from '@/services/indexed-db.service' @@ -158,14 +158,14 @@ class RelaySelectionService { const seenCand = new Set() const candidates: string[] = [] for (const u of [...sessionBoost, ...publicLively]) { - const n = normalizeUrl(u) || u + const n = normalizeAnyRelayUrl(u) || u if (!n || existing.has(n) || seenCand.has(n)) continue seenCand.add(n) candidates.push(n) } const preferred = client.getPreferredRelaysForRandom(candidates, RANDOM_PUBLISH_RELAY_COUNT) preferred.forEach((url) => { - const normalized = normalizeUrl(url) || url + const normalized = normalizeAnyRelayUrl(url) || url addRelay(normalized, 'randomly_selected') randomRelayUrls.push(normalized) }) @@ -365,11 +365,11 @@ class RelaySelectionService { let selectedRelays: string[] = [] + const norm = (url: string) => normalizeAnyRelayUrl(url) || url + // If called with specific relay URLs, use those if (openFrom && openFrom.length > 0) { - selectedRelays = openFrom.map(url => normalizeUrl(url) || url).filter(Boolean) - // Deduplicate the selected relays - selectedRelays = Array.from(new Set(selectedRelays)) + selectedRelays = Array.from(new Set(openFrom.map(norm).filter(Boolean))) } // For discussion replies, use relay hints from the kind 11 + user's outboxes + local relays + thecitadel else if (parentEvent && (parentEvent.kind === ExtendedKind.DISCUSSION || parentEvent.kind === ExtendedKind.COMMENT)) { @@ -381,67 +381,46 @@ class RelaySelectionService { } // For regular replies, use user's write relays + mention relays else if (parentEvent && this.isRegularReply(parentEvent)) { - // Get user's write relays const userRelays = userWriteRelays.length > 0 ? userWriteRelays : FAST_WRITE_RELAY_URLS - selectedRelays = userRelays.map(url => normalizeUrl(url) || url).filter(Boolean) - // Deduplicate the selected relays - selectedRelays = Array.from(new Set(selectedRelays)) - + selectedRelays = Array.from(new Set(userRelays.map(norm).filter(Boolean))) + // Add mention relays if (userPubkey) { let mentions: string[] = [] - - // Always include parent event author for replies - if (parentEvent) { - mentions.push(parentEvent.pubkey) - } - - // Extract additional mentions from content if available + if (parentEvent) mentions.push(parentEvent.pubkey) if (content) { const contentMentions = await this.extractMentions(content, parentEvent) - mentions = [...new Set([...mentions, ...contentMentions])] // deduplicate + mentions = [...new Set([...mentions, ...contentMentions])] } - const mentionedPubkeys = mentions.filter(p => p !== userPubkey) - if (mentionedPubkeys.length > 0) { const mentionRelayLists = await Promise.all( mentionedPubkeys.map(async (pubkey) => { try { - // Use cached version from IndexedDB instead of fetching from relays const relayList = await this.getCachedRelayList(pubkey) if (!relayList) return [] - const userRelays = relayList.write || [] - // Filter out local relays from other users - return this.filterLocalRelaysFromOthers(userRelays) + return this.filterLocalRelaysFromOthers(relayList.write || []) } catch (error) { logger.warn('Failed to get cached relay list', { pubkey, error }) return [] } }) ) - const mentionRelays = mentionRelayLists.flat().map(url => normalizeUrl(url) || url).filter(Boolean) - selectedRelays = [...selectedRelays, ...mentionRelays] - // Deduplicate after adding mention relays - selectedRelays = Array.from(new Set(selectedRelays)) + const mentionRelays = mentionRelayLists.flat().map(norm).filter(Boolean) + selectedRelays = Array.from(new Set([...selectedRelays, ...mentionRelays])) } } } // Default: user's write relays (or fallback to fast write relays if no user relays) else { const defaultRelays = userWriteRelays.length > 0 ? userWriteRelays : FAST_WRITE_RELAY_URLS - selectedRelays = defaultRelays.map(url => normalizeUrl(url) || url).filter(Boolean) - // Deduplicate the selected relays - selectedRelays = Array.from(new Set(selectedRelays)) + selectedRelays = Array.from(new Set(defaultRelays.map(norm).filter(Boolean))) } // ALWAYS include cache relays (local network relays) in selected relays - // Cache relays are important for offline functionality const cacheRelays = userWriteRelays.filter(url => isLocalNetworkUrl(url)) if (cacheRelays.length > 0) { - selectedRelays = [...selectedRelays, ...cacheRelays] - // Deduplicate after adding cache relays - selectedRelays = Array.from(new Set(selectedRelays)) + selectedRelays = Array.from(new Set([...selectedRelays, ...cacheRelays.map(norm).filter(Boolean)])) } // When "add random relays" setting is ON, include random relays in selected by default; when OFF they are still in the list but unchecked @@ -491,7 +470,7 @@ class RelaySelectionService { } senderRelays.forEach(url => { - const normalized = normalizeUrl(url) + const normalized = normalizeAnyRelayUrl(url) if (normalized) { if (!relayToMembers.has(normalized)) { relayToMembers.set(normalized, new Set()) @@ -549,7 +528,7 @@ class RelaySelectionService { recipientRelayLists.forEach((relays, index) => { const pubkey = recipientPubkeys[index] relays.forEach(url => { - const normalized = normalizeUrl(url) + const normalized = normalizeAnyRelayUrl(url) if (normalized) { if (!relayToMembers.has(normalized)) { relayToMembers.set(normalized, new Set()) @@ -615,7 +594,7 @@ class RelaySelectionService { // Normalize and deduplicate final list const normalizedRelays = relays - .map(url => normalizeUrl(url)) + .map(url => normalizeAnyRelayUrl(url)) .filter((url): url is string => !!url) return Array.from(new Set(normalizedRelays)) @@ -623,7 +602,7 @@ class RelaySelectionService { logger.error('Failed to get public message relays', { error, parentEvent: context.parentEvent?.id }) // Fallback to sender's write relays const senderRelays = userWriteRelays.length > 0 ? userWriteRelays : FAST_WRITE_RELAY_URLS - return senderRelays.map(url => normalizeUrl(url) || url).filter(Boolean) + return senderRelays.map(url => normalizeAnyRelayUrl(url) || url).filter(Boolean) } } @@ -642,7 +621,7 @@ class RelaySelectionService { */ private getDiscussionRelayHints(discussionEventId: string): string[] { const eventHints = client.getEventHints(discussionEventId) - return eventHints.map(url => normalizeUrl(url) || url).filter(Boolean) + return eventHints.map(url => normalizeAnyRelayUrl(url) || url).filter(Boolean) } /** @@ -682,7 +661,7 @@ class RelaySelectionService { } // Step 2: Add wss://thecitadel.nostr1.com - const thecitadelUrl = normalizeUrl('wss://thecitadel.nostr1.com') + const thecitadelUrl = normalizeAnyRelayUrl('wss://thecitadel.nostr1.com') if (thecitadelUrl) { relayUrls.add(thecitadelUrl) } @@ -690,7 +669,7 @@ class RelaySelectionService { // Step 3: Add user's outboxes (write relays from kind 10002) if (userWriteRelays.length > 0) { userWriteRelays.forEach(url => { - const normalized = normalizeUrl(url) + const normalized = normalizeAnyRelayUrl(url) if (normalized) { relayUrls.add(normalized) } @@ -701,7 +680,7 @@ class RelaySelectionService { const relayList = await this.getCachedRelayList(userPubkey) if (relayList?.write) { relayList.write.forEach(url => { - const normalized = normalizeUrl(url) + const normalized = normalizeAnyRelayUrl(url) if (normalized) { relayUrls.add(normalized) } @@ -719,7 +698,7 @@ class RelaySelectionService { if (cacheRelayEvent) { cacheRelayEvent.tags.forEach(tag => { if (tag[0] === 'relay' && tag[1]) { - const normalized = normalizeUrl(tag[1]) + const normalized = normalizeAnyRelayUrl(tag[1]) if (normalized) { relayUrls.add(normalized) } @@ -733,7 +712,7 @@ class RelaySelectionService { // Step 5: Convert to array, normalize, and deduplicate const normalizedRelays = Array.from(relayUrls) - .map(url => normalizeUrl(url)) + .map(url => normalizeAnyRelayUrl(url)) .filter((url): url is string => !!url) const deduplicatedRelays = Array.from(new Set(normalizedRelays))