117 lines
3.9 KiB
TypeScript
117 lines
3.9 KiB
TypeScript
/**
|
|
* Relay rotation utility
|
|
* Tries relays in sequence, rotating through the list on failure
|
|
* No retry on individual relay, just move to next and loop
|
|
* Relays that fail are marked inactive for the session
|
|
*/
|
|
|
|
import type { SimplePool } from 'nostr-tools'
|
|
import type { Filter } from 'nostr-tools'
|
|
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
|
|
import { createSubscription } from '@/types/nostr-tools-extended'
|
|
import { relaySessionManager } from './relaySessionManager'
|
|
|
|
/**
|
|
* Try to execute an operation with relay rotation
|
|
* Tries each relay in sequence, moving to next on failure
|
|
* Loops back to first relay after trying all
|
|
*/
|
|
export async function tryWithRelayRotation<T>(
|
|
pool: SimplePool,
|
|
operation: (relayUrl: string, pool: SimplePoolWithSub) => Promise<T>,
|
|
timeout: number = 10000
|
|
): Promise<T> {
|
|
// Get active relays (enabled and not marked inactive for this session)
|
|
const activeRelays = await relaySessionManager.getActiveRelays()
|
|
|
|
if (activeRelays.length === 0) {
|
|
throw new Error('No active relays available')
|
|
}
|
|
|
|
let lastError: Error | null = null
|
|
let attempts = 0
|
|
const maxAttempts = activeRelays.length * 2 // Try all active relays twice (loop once)
|
|
|
|
while (attempts < maxAttempts) {
|
|
// Get current active relays (may have changed if some were marked inactive)
|
|
const currentActiveRelays = await relaySessionManager.getActiveRelays()
|
|
|
|
if (currentActiveRelays.length === 0) {
|
|
throw new Error('No active relays available')
|
|
}
|
|
|
|
const relayIndex = attempts % currentActiveRelays.length
|
|
const relayUrl = currentActiveRelays[relayIndex]
|
|
if (!relayUrl) {
|
|
throw new Error('Invalid relay configuration')
|
|
}
|
|
|
|
// Skip if relay was marked failed during the loop (it will be at the bottom now)
|
|
// We continue to use it but it's lower priority
|
|
|
|
try {
|
|
console.warn(`[RelayRotation] Trying relay ${relayIndex + 1}/${currentActiveRelays.length}: ${relayUrl}`)
|
|
|
|
// Notify progress manager that we're switching to a new relay (reset to 0 for this relay)
|
|
const { syncProgressManager } = await import('./syncProgressManager')
|
|
const currentProgress = syncProgressManager.getProgress()
|
|
if (currentProgress) {
|
|
syncProgressManager.setProgress({
|
|
...currentProgress,
|
|
currentStep: 0, // Reset to 0 when changing relay
|
|
currentRelay: relayUrl,
|
|
})
|
|
}
|
|
|
|
const poolWithSub = pool as unknown as SimplePoolWithSub
|
|
const result = await Promise.race([
|
|
operation(relayUrl, poolWithSub),
|
|
new Promise<never>((_, reject) =>
|
|
setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout)
|
|
),
|
|
])
|
|
console.warn(`[RelayRotation] Success with relay: ${relayUrl}`)
|
|
return result
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
console.warn(`[RelayRotation] Relay ${relayUrl} failed: ${errorMessage}`)
|
|
|
|
// Mark relay as failed (move to bottom of priority list)
|
|
relaySessionManager.markRelayFailed(relayUrl)
|
|
|
|
lastError = error instanceof Error ? error : new Error(String(error))
|
|
attempts++
|
|
|
|
// If we've tried all relays once, loop back
|
|
if (attempts < maxAttempts) {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we get here, all relays failed
|
|
throw lastError ?? new Error('All relays failed')
|
|
}
|
|
|
|
/**
|
|
* Create a subscription with relay rotation
|
|
* Tries each relay until one succeeds
|
|
*/
|
|
export async function createSubscriptionWithRotation(
|
|
pool: SimplePool,
|
|
filters: Filter[],
|
|
timeout: number = 10000
|
|
): Promise<{
|
|
subscription: import('@/types/nostr-tools-extended').Subscription
|
|
relayUrl: string
|
|
}> {
|
|
return tryWithRelayRotation(
|
|
pool,
|
|
async (relayUrl, poolWithSub) => {
|
|
const subscription = createSubscription(poolWithSub, [relayUrl], filters)
|
|
return { subscription, relayUrl }
|
|
},
|
|
timeout
|
|
)
|
|
}
|