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:
padreug 2025-08-10 11:48:33 +02:00
parent df7e461c91
commit 7d7bee8e77
14 changed files with 1982 additions and 955 deletions

View file

@ -0,0 +1,257 @@
<template>
<div class="relay-hub-status">
<div class="status-header">
<h3>Nostr Relay Hub Status</h3>
<div class="connection-indicator" :class="connectionStatus">
{{ connectionStatus }}
</div>
</div>
<div class="connection-info">
<div class="info-row">
<span class="label">Status:</span>
<span class="value">{{ connectionStatus }}</span>
</div>
<div class="info-row">
<span class="label">Connected Relays:</span>
<span class="value">{{ connectedRelayCount }}/{{ totalRelayCount }}</span>
</div>
<div class="info-row">
<span class="label">Health:</span>
<span class="value">{{ connectionHealth.toFixed(1) }}%</span>
</div>
<div class="info-row" v-if="error">
<span class="label">Error:</span>
<span class="value error">{{ error.message }}</span>
</div>
</div>
<div class="relay-list" v-if="relayStatuses.length > 0">
<h4>Relay Status</h4>
<div class="relay-item" v-for="relay in relayStatuses" :key="relay.url">
<div class="relay-url">{{ relay.url }}</div>
<div class="relay-status" :class="{ connected: relay.connected }">
{{ relay.connected ? 'Connected' : 'Disconnected' }}
</div>
<div class="relay-latency" v-if="relay.latency !== undefined">
{{ relay.latency }}ms
</div>
<div class="relay-error" v-if="relay.error">
{{ relay.error }}
</div>
</div>
</div>
<div class="actions">
<button @click="connect" :disabled="isConnected || connectionStatus === 'connecting'">
Connect
</button>
<button @click="disconnect" :disabled="!isConnected">
Disconnect
</button>
<button @click="reconnect" :disabled="connectionStatus === 'connecting'">
Reconnect
</button>
</div>
<div class="subscription-info">
<h4>Active Subscriptions</h4>
<div class="subscription-count">{{ activeSubscriptions.size }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRelayHub } from '@/composables/useRelayHub'
const {
isConnected,
connectionStatus,
relayStatuses,
error,
activeSubscriptions,
connectedRelayCount,
totalRelayCount,
connectionHealth,
connect,
disconnect,
reconnect
} = useRelayHub()
</script>
<style scoped>
.relay-hub-status {
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
background-color: #f8fafc;
max-width: 600px;
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.status-header h3 {
margin: 0;
color: #1e293b;
}
.connection-indicator {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
text-transform: capitalize;
}
.connection-indicator.connected {
background-color: #dcfce7;
color: #166534;
}
.connection-indicator.connecting {
background-color: #fef3c7;
color: #92400e;
}
.connection-indicator.disconnected {
background-color: #fee2e2;
color: #991b1b;
}
.connection-indicator.error {
background-color: #fecaca;
color: #7f1d1d;
}
.connection-info {
margin-bottom: 1.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
border-bottom: 1px solid #e2e8f0;
}
.info-row:last-child {
border-bottom: none;
}
.label {
font-weight: 500;
color: #475569;
}
.value {
color: #1e293b;
}
.value.error {
color: #dc2626;
}
.relay-list {
margin-bottom: 1.5rem;
}
.relay-list h4 {
margin: 0 0 0.75rem 0;
color: #1e293b;
}
.relay-item {
display: grid;
grid-template-columns: 1fr auto auto auto;
gap: 0.75rem;
align-items: center;
padding: 0.75rem;
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
margin-bottom: 0.5rem;
}
.relay-url {
font-family: monospace;
font-size: 0.875rem;
color: #475569;
word-break: break-all;
}
.relay-status {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
text-align: center;
background-color: #fee2e2;
color: #991b1b;
}
.relay-status.connected {
background-color: #dcfce7;
color: #166534;
}
.relay-latency {
font-size: 0.75rem;
color: #64748b;
text-align: center;
}
.relay-error {
font-size: 0.75rem;
color: #dc2626;
text-align: center;
}
.actions {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.actions button {
padding: 0.5rem 1rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
background-color: white;
color: #374151;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
}
.actions button:hover:not(:disabled) {
background-color: #f9fafb;
border-color: #9ca3af;
}
.actions button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.subscription-info {
border-top: 1px solid #e2e8f0;
padding-top: 1rem;
}
.subscription-info h4 {
margin: 0 0 0.5rem 0;
color: #1e293b;
}
.subscription-count {
font-size: 1.5rem;
font-weight: 600;
color: #3b82f6;
text-align: center;
}
</style>

View file

@ -33,6 +33,7 @@ const navigation = computed<NavigationItem[]>(() => [
{ name: t('nav.events'), href: '/events' },
{ name: t('nav.market'), href: '/market' },
{ name: t('nav.chat'), href: '/chat' },
{ name: 'Relay Hub', href: '/relay-hub-demo' },
])
// Compute total wallet balance
@ -149,6 +150,10 @@ const handleLogout = async () => {
<Ticket class="h-4 w-4" />
My Tickets
</DropdownMenuItem>
<DropdownMenuItem @click="() => router.push('/relay-hub-demo')" class="gap-2">
<MessageSquare class="h-4 w-4" />
Relay Hub Demo
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem @click="handleLogout" class="gap-2 text-destructive">
<LogOut class="h-4 w-4" />
@ -230,6 +235,11 @@ const handleLogout = async () => {
<Ticket class="h-4 w-4" />
My Tickets
</Button>
<Button variant="ghost" size="sm" @click="() => router.push('/relay-hub-demo')"
class="w-full justify-start gap-2">
<MessageSquare class="h-4 w-4" />
Relay Hub Demo
</Button>
<Button variant="ghost" size="sm" @click="handleLogout"
class="w-full justify-start gap-2 text-destructive">
<LogOut class="h-4 w-4" />