Browse Source

quiet the console

and fix relay list
imwald
Silberengel 3 weeks ago
parent
commit
ed2ca2a7e9
  1. 151
      src/components/PostEditor/PostRelaySelector.tsx
  2. 156
      src/lib/logger.ts
  3. 71
      src/services/relay-selection.service.ts

151
src/components/PostEditor/PostRelaySelector.tsx

@ -17,7 +17,7 @@ import { userReadRelaysWithHttp } from '@/lib/favorites-feed-relays' @@ -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({ @@ -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<string[]>([])
// 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({ @@ -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({ @@ -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({ @@ -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({ @@ -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) {

156
src/lib/logger.ts

@ -1,149 +1,127 @@ @@ -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)
}
}

71
src/services/relay-selection.service.ts

@ -3,7 +3,7 @@ import { ExtendedKind, FAST_WRITE_RELAY_URLS, RANDOM_PUBLISH_RELAY_COUNT } from @@ -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 { @@ -158,14 +158,14 @@ class RelaySelectionService {
const seenCand = new Set<string>()
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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))

Loading…
Cancel
Save