feat: Integrate Relay Hub for centralized Nostr connection management
- Introduce a new composable, useRelayHub, to manage all Nostr WebSocket connections, enhancing connection stability and performance. - Update existing components and composables to utilize the Relay Hub for connecting, publishing events, and subscribing to updates, streamlining the overall architecture. - Add a RelayHubStatus component to display connection status and health metrics, improving user feedback on the connection state. - Implement a RelayHubDemo page to showcase the functionality of the Relay Hub, including connection tests and subscription management. - Ensure proper error handling and logging throughout the integration process to facilitate debugging and user experience.
This commit is contained in:
parent
df7e461c91
commit
7d7bee8e77
14 changed files with 1982 additions and 955 deletions
267
src/composables/useRelayHub.ts
Normal file
267
src/composables/useRelayHub.ts
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { relayHub, type SubscriptionConfig, type RelayStatus } from '../lib/nostr/relayHub'
|
||||
import { config } from '../lib/config'
|
||||
|
||||
export function useRelayHub() {
|
||||
// Reactive state
|
||||
const isConnected = ref(false)
|
||||
const connectionStatus = ref<'connecting' | 'connected' | 'disconnected' | 'error'>('disconnected')
|
||||
const relayStatuses = ref<RelayStatus[]>([])
|
||||
const error = ref<Error | null>(null)
|
||||
const activeSubscriptions = ref<Set<string>>(new Set())
|
||||
|
||||
// Computed properties
|
||||
const connectedRelayCount = computed(() => relayHub.connectedRelayCount)
|
||||
const totalRelayCount = computed(() => relayHub.totalRelayCount)
|
||||
const connectionHealth = computed(() => {
|
||||
if (totalRelayCount.value === 0) return 0
|
||||
return (connectedRelayCount.value / totalRelayCount.value) * 100
|
||||
})
|
||||
|
||||
// Initialize relay hub
|
||||
const initialize = async (): Promise<void> => {
|
||||
try {
|
||||
connectionStatus.value = 'connecting'
|
||||
error.value = null
|
||||
|
||||
// Get relay URLs from config
|
||||
const relayUrls = config.nostr.relays
|
||||
if (!relayUrls || relayUrls.length === 0) {
|
||||
throw new Error('No relay URLs configured')
|
||||
}
|
||||
|
||||
// Initialize the relay hub
|
||||
await relayHub.initialize(relayUrls)
|
||||
|
||||
// Set up event listeners
|
||||
setupEventListeners()
|
||||
|
||||
connectionStatus.value = 'connected'
|
||||
isConnected.value = true
|
||||
|
||||
console.log('RelayHub initialized successfully')
|
||||
} catch (err) {
|
||||
const errorObj = err instanceof Error ? err : new Error('Failed to initialize RelayHub')
|
||||
error.value = errorObj
|
||||
connectionStatus.value = 'error'
|
||||
isConnected.value = false
|
||||
console.error('Failed to initialize RelayHub:', errorObj)
|
||||
throw errorObj
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to relays
|
||||
const connect = async (): Promise<void> => {
|
||||
try {
|
||||
if (!relayHub.isInitialized) {
|
||||
await initialize()
|
||||
return
|
||||
}
|
||||
|
||||
connectionStatus.value = 'connecting'
|
||||
error.value = null
|
||||
|
||||
await relayHub.connect()
|
||||
|
||||
connectionStatus.value = 'connected'
|
||||
isConnected.value = true
|
||||
} catch (err) {
|
||||
const errorObj = err instanceof Error ? err : new Error('Failed to connect')
|
||||
error.value = errorObj
|
||||
connectionStatus.value = 'error'
|
||||
isConnected.value = false
|
||||
throw errorObj
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect from relays
|
||||
const disconnect = (): void => {
|
||||
relayHub.disconnect()
|
||||
connectionStatus.value = 'disconnected'
|
||||
isConnected.value = false
|
||||
error.value = null
|
||||
}
|
||||
|
||||
// Subscribe to events
|
||||
const subscribe = (config: SubscriptionConfig): (() => void) => {
|
||||
try {
|
||||
const unsubscribe = relayHub.subscribe(config)
|
||||
activeSubscriptions.value.add(config.id)
|
||||
|
||||
// Return enhanced unsubscribe function
|
||||
return () => {
|
||||
unsubscribe()
|
||||
activeSubscriptions.value.delete(config.id)
|
||||
}
|
||||
} catch (err) {
|
||||
const errorObj = err instanceof Error ? err : new Error('Failed to subscribe')
|
||||
error.value = errorObj
|
||||
throw errorObj
|
||||
}
|
||||
}
|
||||
|
||||
// Publish an event
|
||||
const publishEvent = async (event: any): Promise<{ success: number; total: number }> => {
|
||||
try {
|
||||
return await relayHub.publishEvent(event)
|
||||
} catch (err) {
|
||||
const errorObj = err instanceof Error ? err : new Error('Failed to publish event')
|
||||
error.value = errorObj
|
||||
throw errorObj
|
||||
}
|
||||
}
|
||||
|
||||
// Query events (one-time fetch)
|
||||
const queryEvents = async (filters: any[], relays?: string[]): Promise<any[]> => {
|
||||
try {
|
||||
return await relayHub.queryEvents(filters, relays)
|
||||
} catch (err) {
|
||||
const errorObj = err instanceof Error ? err : new Error('Failed to query events')
|
||||
error.value = errorObj
|
||||
throw errorObj
|
||||
}
|
||||
}
|
||||
|
||||
// Force reconnection
|
||||
const reconnect = async (): Promise<void> => {
|
||||
try {
|
||||
connectionStatus.value = 'connecting'
|
||||
error.value = null
|
||||
|
||||
await relayHub.reconnect()
|
||||
|
||||
connectionStatus.value = 'connected'
|
||||
isConnected.value = true
|
||||
} catch (err) {
|
||||
const errorObj = err instanceof Error ? err : new Error('Failed to reconnect')
|
||||
error.value = errorObj
|
||||
connectionStatus.value = 'error'
|
||||
isConnected.value = false
|
||||
throw errorObj
|
||||
}
|
||||
}
|
||||
|
||||
// Get relay status
|
||||
const getRelayStatus = (url: string): RelayStatus | undefined => {
|
||||
return relayStatuses.value.find(status => status.url === url)
|
||||
}
|
||||
|
||||
// Check if a specific relay is connected
|
||||
const isRelayConnected = (url: string): boolean => {
|
||||
return relayHub.isRelayConnected(url)
|
||||
}
|
||||
|
||||
// Setup event listeners for the relay hub
|
||||
const setupEventListeners = (): void => {
|
||||
// Connection events
|
||||
relayHub.on('connected', (count: number) => {
|
||||
console.log(`Connected to ${count} relays`)
|
||||
isConnected.value = true
|
||||
connectionStatus.value = 'connected'
|
||||
error.value = null
|
||||
})
|
||||
|
||||
relayHub.on('disconnected', () => {
|
||||
console.log('Disconnected from all relays')
|
||||
isConnected.value = false
|
||||
connectionStatus.value = 'disconnected'
|
||||
})
|
||||
|
||||
relayHub.on('connectionError', (err: Error) => {
|
||||
console.error('Connection error:', err)
|
||||
error.value = err
|
||||
connectionStatus.value = 'error'
|
||||
isConnected.value = false
|
||||
})
|
||||
|
||||
relayHub.on('allRelaysDisconnected', () => {
|
||||
console.warn('All relays disconnected')
|
||||
isConnected.value = false
|
||||
connectionStatus.value = 'disconnected'
|
||||
})
|
||||
|
||||
relayHub.on('partialDisconnection', ({ connected, total }: { connected: number; total: number }) => {
|
||||
console.warn(`Partial disconnection: ${connected}/${total} relays connected`)
|
||||
isConnected.value = connected > 0
|
||||
connectionStatus.value = connected > 0 ? 'connected' : 'disconnected'
|
||||
})
|
||||
|
||||
relayHub.on('maxReconnectAttemptsReached', () => {
|
||||
console.error('Max reconnection attempts reached')
|
||||
connectionStatus.value = 'error'
|
||||
isConnected.value = false
|
||||
error.value = new Error('Max reconnection attempts reached')
|
||||
})
|
||||
|
||||
relayHub.on('networkOffline', () => {
|
||||
console.log('Network went offline')
|
||||
connectionStatus.value = 'disconnected'
|
||||
isConnected.value = false
|
||||
})
|
||||
|
||||
// Update relay statuses periodically
|
||||
const updateRelayStatuses = () => {
|
||||
relayStatuses.value = relayHub.relayStatuses
|
||||
}
|
||||
|
||||
// Update immediately and then every 10 seconds
|
||||
updateRelayStatuses()
|
||||
const statusInterval = setInterval(updateRelayStatuses, 10000)
|
||||
|
||||
// Cleanup interval on unmount
|
||||
onUnmounted(() => {
|
||||
clearInterval(statusInterval)
|
||||
})
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
const cleanup = (): void => {
|
||||
// Close all active subscriptions
|
||||
activeSubscriptions.value.forEach(subId => {
|
||||
relayHub.unsubscribe(subId)
|
||||
})
|
||||
activeSubscriptions.value.clear()
|
||||
}
|
||||
|
||||
// Auto-initialize on mount if config is available
|
||||
onMounted(async () => {
|
||||
try {
|
||||
if (config.nostr.relays && config.nostr.relays.length > 0) {
|
||||
await initialize()
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Auto-initialization failed:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup on unmount
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
return {
|
||||
// State
|
||||
isConnected,
|
||||
connectionStatus,
|
||||
relayStatuses,
|
||||
error,
|
||||
activeSubscriptions,
|
||||
|
||||
// Computed
|
||||
connectedRelayCount,
|
||||
totalRelayCount,
|
||||
connectionHealth,
|
||||
|
||||
// Methods
|
||||
initialize,
|
||||
connect,
|
||||
disconnect,
|
||||
subscribe,
|
||||
publishEvent,
|
||||
queryEvents,
|
||||
reconnect,
|
||||
getRelayStatus,
|
||||
isRelayConnected,
|
||||
cleanup
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue