@ -142,9 +142,12 @@ class ClientService extends EventTarget {
// Use current user's relay list
// Use current user's relay list
const relayList = this . pubkey ? await this . fetchRelayList ( this . pubkey ) : { write : [ ] , read : [ ] }
const relayList = this . pubkey ? await this . fetchRelayList ( this . pubkey ) : { write : [ ] , read : [ ] }
console . log ( 'DEBUG: User relay list write URLs:' , relayList ? . write )
const senderWriteRelays = relayList ? . write . slice ( 0 , 6 ) ? ? [ ]
const senderWriteRelays = relayList ? . write . slice ( 0 , 6 ) ? ? [ ]
console . log ( 'DEBUG: Selected sender write relays:' , senderWriteRelays )
const recipientReadRelays = Array . from ( new Set ( _additionalRelayUrls ) )
const recipientReadRelays = Array . from ( new Set ( _additionalRelayUrls ) )
relays = senderWriteRelays . concat ( recipientReadRelays )
relays = senderWriteRelays . concat ( recipientReadRelays )
console . log ( 'DEBUG: Final relay URLs before optimization:' , relays )
}
}
if ( ! relays . length ) {
if ( ! relays . length ) {
@ -238,6 +241,7 @@ class ClientService extends EventTarget {
totalCount : number
totalCount : number
} > {
} > {
const uniqueRelayUrls = this . optimizeRelaySelection ( Array . from ( new Set ( relayUrls ) ) )
const uniqueRelayUrls = this . optimizeRelaySelection ( Array . from ( new Set ( relayUrls ) ) )
console . log ( 'DEBUG: uniqueRelayUrls after optimization:' , uniqueRelayUrls )
const relayStatuses : Array < {
const relayStatuses : Array < {
url : string
url : string
@ -260,13 +264,17 @@ class ClientService extends EventTarget {
const checkCompletion = ( ) = > {
const checkCompletion = ( ) = > {
if ( resolved ) return
if ( resolved ) return
// If one third of the relays have accepted the event, consider it a success
// Wait for all relays to complete before resolving (don't complete early)
const isSuccess = successCount >= Math . max ( 1 , Math . ceil ( uniqueRelayUrls . length / 3 ) )
// This ensures we show the full relay status information
if ( isSuccess && ! resolved ) {
if ( finishedCount >= uniqueRelayUrls . length && ! resolved ) {
this . emitNewEvent ( event )
const isSuccess = successCount > 0
if ( isSuccess ) {
this . emitNewEvent ( event )
}
resolved = true
resolved = true
console . log ( 'DEBUG: Publishing completed. relayStatuses:' , relayStatuses )
resolve ( {
resolve ( {
success : true ,
success : isSuccess ,
relayStatuses ,
relayStatuses ,
successCount ,
successCount ,
totalCount : uniqueRelayUrls.length
totalCount : uniqueRelayUrls.length
@ -274,35 +282,26 @@ class ClientService extends EventTarget {
return
return
}
}
if ( finishedCount >= uniqueRelayUrls . length && ! resolved ) {
// Handle case where no relays succeed
if ( successCount > 0 ) {
if ( finishedCount >= uniqueRelayUrls . length && ! resolved && successCount === 0 ) {
this . emitNewEvent ( event )
resolved = true
resolved = true
console . log ( 'DEBUG: All relays failed. relayStatuses:' , relayStatuses )
resolve ( {
const aggregateError = new AggregateError (
success : true ,
errors . map (
relayStatuses ,
( { url , error } ) = > {
successCount ,
let errorMsg = 'Unknown error'
totalCount : uniqueRelayUrls.length
if ( error instanceof Error ) {
} )
errorMsg = error . message || 'Empty error message'
} else {
} else if ( error !== null && error !== undefined ) {
resolved = true
errorMsg = String ( error )
const aggregateError = new AggregateError (
errors . map (
( { url , error } ) = > {
let errorMsg = 'Unknown error'
if ( error instanceof Error ) {
errorMsg = error . message || 'Empty error message'
} else if ( error !== null && error !== undefined ) {
errorMsg = String ( error )
}
return new Error ( ` Failed to publish to ${ url } : ${ errorMsg } ` )
}
}
)
return new Error ( ` Failed to publish to ${ url } : ${ errorMsg } ` )
}
)
)
// Attach relay statuses to the error so they can be displayed
)
; ( aggregateError as any ) . relayStatuses = relayStatuses
// Attach relay statuses to the error so they can be displayed
reject ( aggregateError )
; ( aggregateError as any ) . relayStatuses = relayStatuses
}
reject ( aggregateError )
}
}
}
}
@ -329,6 +328,7 @@ class ClientService extends EventTarget {
// eslint-disable-next-line @typescript-eslint/no-this-alias
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this
const that = this
console . log ( 'DEBUG: Attempting to publish to relay:' , url )
try {
try {
// Throttle requests to prevent "too many concurrent REQs" errors
// Throttle requests to prevent "too many concurrent REQs" errors
await this . throttleRequest ( url )
await this . throttleRequest ( url )
@ -336,7 +336,9 @@ class ClientService extends EventTarget {
const relay = await this . pool . ensureRelay ( url )
const relay = await this . pool . ensureRelay ( url )
relay . publishTimeout = 8 _000 // 8s
relay . publishTimeout = 8 _000 // 8s
console . log ( 'DEBUG: Publishing to relay:' , url )
await relay . publish ( event )
await relay . publish ( event )
console . log ( 'DEBUG: Successfully published to relay:' , url )
this . trackEventSeenOn ( event . id , relay )
this . trackEventSeenOn ( event . id , relay )
this . recordSuccess ( url )
this . recordSuccess ( url )
successCount ++
successCount ++
@ -349,6 +351,7 @@ class ClientService extends EventTarget {
checkCompletion ( )
checkCompletion ( )
} catch ( error ) {
} catch ( error ) {
console . log ( 'DEBUG: Failed to publish to relay:' , url , 'Error:' , error )
let errorMessage = 'Unknown error'
let errorMessage = 'Unknown error'
if ( error instanceof Error ) {
if ( error instanceof Error ) {
errorMessage = error . message || 'Empty error message'
errorMessage = error . message || 'Empty error message'
@ -384,17 +387,31 @@ class ClientService extends EventTarget {
error . message . startsWith ( 'auth-required' ) &&
error . message . startsWith ( 'auth-required' ) &&
! ! that . signer
! ! that . signer
) {
) {
console . log ( 'DEBUG: Attempting authentication for relay:' , url )
try {
try {
// Throttle auth requests too
// Throttle auth requests too
await this . throttleRequest ( url )
await this . throttleRequest ( url )
const relay = await this . pool . ensureRelay ( url )
const relay = await this . pool . ensureRelay ( url )
await relay . auth ( ( authEvt : EventTemplate ) = > {
// Attempt auth with proper timeout handling
console . log ( 'DEBUG: Starting auth for relay:' , url )
const authPromise = relay . auth ( ( authEvt : EventTemplate ) = > {
// Ensure the auth event has the correct pubkey
// Ensure the auth event has the correct pubkey
const authEventWithPubkey = { . . . authEvt , pubkey : that.pubkey }
const authEventWithPubkey = { . . . authEvt , pubkey : that.pubkey }
return that . signer ! . signEvent ( authEventWithPubkey )
return that . signer ! . signEvent ( authEventWithPubkey )
} )
} )
const authTimeoutPromise = new Promise ( ( _ , reject ) = > {
setTimeout ( ( ) = > reject ( new Error ( 'Auth timeout' ) ) , 8000 ) // 8s timeout
} )
await Promise . race ( [ authPromise , authTimeoutPromise ] )
console . log ( 'DEBUG: Auth successful for relay:' , url )
await relay . publish ( event )
await relay . publish ( event )
console . log ( 'DEBUG: Publish successful for relay:' , url )
this . trackEventSeenOn ( event . id , relay )
this . trackEventSeenOn ( event . id , relay )
this . recordSuccess ( url )
this . recordSuccess ( url )
successCount ++
successCount ++
@ -408,6 +425,7 @@ class ClientService extends EventTarget {
checkCompletion ( )
checkCompletion ( )
} catch ( authError ) {
} catch ( authError ) {
console . log ( 'DEBUG: Auth failed for relay:' , url , 'Error:' , authError )
let authErrorMessage = 'Unknown auth error'
let authErrorMessage = 'Unknown auth error'
if ( authError instanceof Error ) {
if ( authError instanceof Error ) {
authErrorMessage = authError . message || 'Empty auth error message'
authErrorMessage = authError . message || 'Empty auth error message'
@ -1479,7 +1497,7 @@ class ClientService extends EventTarget {
return getRelayListFromEvent ( event )
return getRelayListFromEvent ( event )
}
}
return {
return {
write : BIG_RELAY_URLS ,
write : Array.from ( new Set ( [ . . . FAST_WRITE_RELAY_URLS , . . . BIG_RELAY_URLS ] ) ) . slice ( 0 , 8 ) , // Combine fast write + big relays for better redundancy (deduplicated)
read : BIG_RELAY_URLS ,
read : BIG_RELAY_URLS ,
originalRelays : [ ]
originalRelays : [ ]
}
}
@ -1730,11 +1748,6 @@ class ClientService extends EventTarget {
// Skip empty or invalid URLs
// Skip empty or invalid URLs
if ( ! url || typeof url !== 'string' ) return false
if ( ! url || typeof url !== 'string' ) return false
// Skip localhost URLs that might be misconfigured
if ( url . includes ( 'localhost:7777' ) || url . includes ( 'localhost:5173' ) ) {
return false
}
// Skip relays with open circuit breaker
// Skip relays with open circuit breaker
if ( this . isCircuitBreakerOpen ( url ) ) {
if ( this . isCircuitBreakerOpen ( url ) ) {
console . warn ( ` Skipping relay with open circuit breaker: ${ url } ` )
console . warn ( ` Skipping relay with open circuit breaker: ${ url } ` )