feat: Add NOSTR architecture documentation
- Introduce a comprehensive documentation file detailing the singleton-based architecture of the Ario web app. - Outline the core singletons for authentication, relay management, chat, market, and Nostr store, along with their integration into components. - Provide insights into the benefits of the architecture, including resource management, event handling, and performance optimizations. - Include guidelines for configuration, error handling, and future extensibility to support ongoing development.
This commit is contained in:
parent
91d742eb7b
commit
a4584ed9bd
1 changed files with 782 additions and 0 deletions
782
NOSTR_ARCHITECTURE.md
Normal file
782
NOSTR_ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,782 @@
|
|||
# Ario Web App Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The Ario web app uses a **singleton-based architecture** to manage shared state and resources across components. This document explains how the core singletons work and how different components (chat, market, feed) plug into the system.
|
||||
|
||||
## Core Singleton Architecture
|
||||
|
||||
### 1. Authentication Singleton (`useAuth`)
|
||||
|
||||
**Location**: `src/composables/useAuth.ts`
|
||||
|
||||
**Purpose**: Manages user authentication state using LNBits integration.
|
||||
|
||||
**Singleton Pattern**:
|
||||
```typescript
|
||||
export function useAuth() {
|
||||
// ... implementation
|
||||
}
|
||||
|
||||
// Export singleton instance for global state
|
||||
export const auth = useAuth()
|
||||
```
|
||||
|
||||
**Usage**: Imported throughout the app for authentication state:
|
||||
```typescript
|
||||
import { auth } from '@/composables/useAuth'
|
||||
|
||||
// All components see the same authentication state
|
||||
console.log(auth.isAuthenticated.value)
|
||||
```
|
||||
|
||||
### 2. Relay Hub Singleton (`relayHub`)
|
||||
|
||||
**Location**: `src/lib/nostr/relayHub.ts`
|
||||
|
||||
**Purpose**: Centralized management of Nostr relay connections, providing a single WebSocket connection pool for the entire application.
|
||||
|
||||
**Singleton Pattern**:
|
||||
```typescript
|
||||
export class RelayHub extends EventEmitter {
|
||||
// ... implementation
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const relayHub = new RelayHub()
|
||||
```
|
||||
|
||||
**Key Features**:
|
||||
- Single WebSocket connection pool
|
||||
- Automatic reconnection and health checks
|
||||
- Event broadcasting for connection state changes
|
||||
- Mobile visibility handling for WebSocket management
|
||||
|
||||
### 3. Relay Hub Composable Singleton (`useRelayHub`)
|
||||
|
||||
**Location**: `src/composables/useRelayHub.ts`
|
||||
|
||||
**Purpose**: Vue-reactive wrapper around the core `relayHub` instance, providing reactive state and Vue-specific functionality.
|
||||
|
||||
**Singleton Pattern**:
|
||||
```typescript
|
||||
export function useRelayHub() {
|
||||
// Uses the same relayHub instance
|
||||
const initialize = async () => {
|
||||
await relayHub.initialize(relayUrls)
|
||||
}
|
||||
|
||||
return { initialize, connect, disconnect, ... }
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const relayHubComposable = useRelayHub()
|
||||
```
|
||||
|
||||
**Relationship**: This composable wraps the core `relayHub` instance, making it Vue-reactive while maintaining the singleton connection pool.
|
||||
|
||||
### 4. Chat Singleton (`useNostrChat`)
|
||||
|
||||
**Location**: `src/composables/useNostrChat.ts`
|
||||
|
||||
**Purpose**: Manages Nostr chat functionality including message encryption/decryption, peer management, and real-time messaging.
|
||||
|
||||
**Singleton Pattern**:
|
||||
```typescript
|
||||
export function useNostrChat() {
|
||||
// ... implementation
|
||||
}
|
||||
|
||||
// Export singleton instance for global state
|
||||
export const nostrChat = useNostrChat()
|
||||
```
|
||||
|
||||
**Relay Hub Integration**: Uses `useRelayHub()` to connect to relays and manage subscriptions.
|
||||
|
||||
### 5. Market Singleton (`useMarket`)
|
||||
|
||||
**Location**: `src/composables/useMarket.ts`
|
||||
|
||||
**Purpose**: Manages market functionality including stalls, products, and market data.
|
||||
|
||||
**Singleton Pattern**:
|
||||
```typescript
|
||||
export function useMarket() {
|
||||
// ... implementation
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const market = useMarket()
|
||||
```
|
||||
|
||||
**Relay Hub Integration**: Uses `useRelayHub()` to fetch market data from Nostr relays.
|
||||
|
||||
### 6. Nostr Store Singleton (`useNostrStore`)
|
||||
|
||||
**Location**: `src/stores/nostr.ts`
|
||||
|
||||
**Purpose**: Pinia store for Nostr-related state including push notifications and relay connection status.
|
||||
|
||||
**Singleton Pattern**:
|
||||
```typescript
|
||||
export const useNostrStore = defineStore('nostr', () => {
|
||||
// ... implementation
|
||||
})
|
||||
```
|
||||
|
||||
**Relay Hub Integration**: Imports the `relayHub` singleton directly for connection management.
|
||||
|
||||
## Component Integration Architecture
|
||||
|
||||
### How Components Plug Into the System
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Component Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ChatComponent │ NostrFeed │ Market.vue │ Navbar.vue │
|
||||
│ ↓ │ ↓ │ ↓ │ ↓ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Composable Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ useNostrChat │ useRelayHub │ useMarket │ useAuth │
|
||||
│ ↓ │ ↓ │ ↓ │ ↓ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Singleton Instance Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ nostrChat │ relayHub │ market │ auth │
|
||||
│ ↓ │ ↓ │ ↓ │ ↓ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Core Implementation Layer │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ RelayHub │ LNBits API │ Market API │ Chat API │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1. Chat Component Integration
|
||||
|
||||
**Component**: `src/components/nostr/ChatComponent.vue`
|
||||
|
||||
**Integration Path**:
|
||||
```typescript
|
||||
// 1. Component imports the chat singleton
|
||||
import { nostrChat } from '@/composables/useNostrChat'
|
||||
|
||||
// 2. Chat singleton uses relay hub composable
|
||||
import { useRelayHub } from './useRelayHub'
|
||||
|
||||
// 3. Relay hub composable uses core relay hub singleton
|
||||
import { relayHub } from '../lib/nostr/relayHub'
|
||||
```
|
||||
|
||||
**Data Flow**:
|
||||
```
|
||||
ChatComponent → nostrChat → useRelayHub → relayHub → Nostr Relays
|
||||
↓ ↓ ↓ ↓
|
||||
UI Updates Chat State Vue State WebSocket
|
||||
```
|
||||
|
||||
**Key Benefits**:
|
||||
- Single chat state across all components
|
||||
- Shared relay connections
|
||||
- Centralized message encryption/decryption
|
||||
- Unified peer management
|
||||
|
||||
### 2. Feed Component Integration
|
||||
|
||||
**Component**: `src/components/nostr/NostrFeed.vue`
|
||||
|
||||
**Integration Path**:
|
||||
```typescript
|
||||
// 1. Component imports relay hub composable
|
||||
import { useRelayHub } from '@/composables/useRelayHub'
|
||||
|
||||
// 2. Relay hub composable uses core relay hub singleton
|
||||
import { relayHub } from '../lib/nostr/relayHub'
|
||||
```
|
||||
|
||||
**Data Flow**:
|
||||
```
|
||||
NostrFeed → useRelayHub → relayHub → Nostr Relays
|
||||
↓ ↓ ↓
|
||||
Feed UI Vue State WebSocket
|
||||
```
|
||||
|
||||
**Key Benefits**:
|
||||
- Real-time feed updates
|
||||
- Shared relay connections
|
||||
- Centralized admin filtering
|
||||
- Efficient event subscription management
|
||||
|
||||
### 3. Market Component Integration
|
||||
|
||||
**Component**: `src/pages/Market.vue`
|
||||
|
||||
**Integration Path**:
|
||||
```typescript
|
||||
// 1. Component uses market composable
|
||||
import { useMarket } from '@/composables/useMarket'
|
||||
|
||||
// 2. Market composable uses relay hub composable
|
||||
import { useRelayHub } from './useRelayHub'
|
||||
|
||||
// 3. Relay hub composable uses core relay hub singleton
|
||||
import { relayHub } from '../lib/nostr/relayHub'
|
||||
```
|
||||
|
||||
**Data Flow**:
|
||||
```
|
||||
Market.vue → useMarket → useRelayHub → relayHub → Nostr Relays
|
||||
↓ ↓ ↓ ↓
|
||||
Market UI Market Vue State WebSocket
|
||||
State
|
||||
```
|
||||
|
||||
**Key Benefits**:
|
||||
- Centralized market data
|
||||
- Shared relay connections
|
||||
- Efficient product/stall fetching
|
||||
- Real-time market updates
|
||||
|
||||
## Singleton Benefits in Practice
|
||||
|
||||
### 1. **Shared Connection State**
|
||||
```typescript
|
||||
// Component A
|
||||
const { isConnected } = useRelayHub()
|
||||
console.log(isConnected.value) // true
|
||||
|
||||
// Component B (different part of app)
|
||||
const { isConnected } = useRelayHub()
|
||||
console.log(isConnected.value) // true (same state!)
|
||||
```
|
||||
|
||||
### 2. **Centralized Resource Management**
|
||||
```typescript
|
||||
// All components share the same WebSocket connections
|
||||
relayHub.connectedRelayCount // Same value everywhere
|
||||
relayHub.totalSubscriptionCount // Same value everywhere
|
||||
```
|
||||
|
||||
### 3. **Event Broadcasting**
|
||||
```typescript
|
||||
// Component A listens to connection events
|
||||
relayHub.on('connected', () => console.log('Connected!'))
|
||||
|
||||
// Component B triggers connection
|
||||
await relayHub.connect() // Component A gets notified!
|
||||
```
|
||||
|
||||
### 4. **Efficient Resource Usage**
|
||||
- **Before**: Each component creates its own relay connections
|
||||
- **After**: Single connection pool shared across all components
|
||||
- **Result**: Reduced memory usage, better performance, consistent state
|
||||
|
||||
## Configuration and Environment Variables
|
||||
|
||||
### Required Environment Variables
|
||||
```bash
|
||||
# Nostr relay configuration
|
||||
VITE_NOSTR_RELAYS=["wss://relay1.example.com", "wss://relay2.example.com"]
|
||||
|
||||
# Admin pubkeys for feed filtering
|
||||
VITE_ADMIN_PUBKEYS=["npub1admin1...", "npub1admin2..."]
|
||||
|
||||
# LNBits API configuration
|
||||
VITE_LNBITS_BASE_URL=https://your-lnbits-instance.com
|
||||
VITE_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
### Configuration Structure
|
||||
```typescript
|
||||
// src/lib/config/index.ts
|
||||
export const config: AppConfig = {
|
||||
nostr: {
|
||||
relays: parseJsonEnv(import.meta.env.VITE_NOSTR_RELAYS, []),
|
||||
adminPubkeys: parseJsonEnv(import.meta.env.VITE_ADMIN_PUBKEYS, [])
|
||||
},
|
||||
api: {
|
||||
baseUrl: import.meta.env.VITE_LNBITS_BASE_URL || '',
|
||||
key: import.meta.env.VITE_API_KEY || ''
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing and Development
|
||||
|
||||
### Mocking Singletons for Testing
|
||||
```typescript
|
||||
// In test files, you can mock the singleton instances
|
||||
import { auth } from '@/composables/useAuth'
|
||||
import { nostrChat } from '@/composables/useNostrChat'
|
||||
|
||||
// Mock the singletons
|
||||
vi.mock('@/composables/useAuth', () => ({
|
||||
auth: {
|
||||
isAuthenticated: ref(false),
|
||||
currentUser: ref(null)
|
||||
}
|
||||
}))
|
||||
```
|
||||
|
||||
### Development Benefits
|
||||
- **Hot Reload**: Singleton state persists across component reloads
|
||||
- **Debugging**: Single point of inspection for shared state
|
||||
- **Performance**: No unnecessary re-initialization of expensive resources
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. **Always Use the Singleton Instance**
|
||||
```typescript
|
||||
// ✅ Good: Use the exported singleton
|
||||
import { auth } from '@/composables/useAuth'
|
||||
import { nostrChat } from '@/composables/useNostrChat'
|
||||
|
||||
// ❌ Bad: Don't create new instances
|
||||
const { currentUser } = useAuth() // This creates a new instance!
|
||||
```
|
||||
|
||||
### 2. **Access State Through Composables**
|
||||
```typescript
|
||||
// ✅ Good: Use the composable wrapper for reactive state
|
||||
const { isConnected, connectionStatus } = useRelayHub()
|
||||
|
||||
// ❌ Bad: Don't access relayHub directly in components
|
||||
import { relayHub } from '@/lib/nostr/relayHub'
|
||||
console.log(relayHub.isConnected) // Not reactive!
|
||||
```
|
||||
|
||||
### 3. **Listen to Events for State Changes**
|
||||
```typescript
|
||||
// ✅ Good: Listen to relay hub events
|
||||
relayHub.on('connected', () => {
|
||||
console.log('Relay connected!')
|
||||
})
|
||||
|
||||
// ❌ Bad: Don't poll for state changes
|
||||
setInterval(() => {
|
||||
console.log(relayHub.isConnected) // Inefficient!
|
||||
}, 1000)
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The singleton architecture in Ario provides:
|
||||
|
||||
1. **Efficient Resource Management**: Single connection pools and shared state
|
||||
2. **Consistent User Experience**: All components see the same data
|
||||
3. **Simplified Development**: Clear patterns for state management
|
||||
4. **Better Performance**: No duplicate connections or state
|
||||
5. **Easier Testing**: Centralized mocking of shared resources
|
||||
|
||||
This architecture makes the app more maintainable, performant, and user-friendly while providing a solid foundation for future features.
|
||||
|
||||
## Shared Nostr Function Execution
|
||||
|
||||
### Event Building and Publishing Pattern
|
||||
|
||||
All components that need to create and publish Nostr events follow a consistent pattern through the relay hub singleton:
|
||||
|
||||
#### 1. Event Creation Utilities
|
||||
|
||||
**Direct Event Template Creation** (used in `useNostrChat`):
|
||||
```typescript
|
||||
import { nip04, finalizeEvent, type EventTemplate } from 'nostr-tools'
|
||||
|
||||
// Create event template
|
||||
const eventTemplate: EventTemplate = {
|
||||
kind: 4, // Encrypted DM
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [['p', peerPubkey]]
|
||||
content: encryptedContent
|
||||
}
|
||||
|
||||
// Finalize and sign the event
|
||||
const event = finalizeEvent(eventTemplate, hexToBytes(privateKey))
|
||||
```
|
||||
|
||||
**Market Event Creation** (used in `useMarket`):
|
||||
```typescript
|
||||
// Market events use specific kinds and tags
|
||||
const MARKET_EVENT_KINDS = {
|
||||
STALL: 30017,
|
||||
PRODUCT: 30018,
|
||||
MARKET: 30019
|
||||
}
|
||||
|
||||
// Extract data from event tags
|
||||
const stallId = event.tags.find((tag: any) => tag[0] === 'd')?.[1]
|
||||
const productId = event.tags.find((tag: any) => tag[0] === 'd')?.[1]
|
||||
```
|
||||
|
||||
#### 2. Centralized Publishing Through Relay Hub
|
||||
|
||||
**All components publish through the same singleton**:
|
||||
```typescript
|
||||
// In useNostrChat.ts
|
||||
await relayHub.publishEvent(event)
|
||||
|
||||
// In useMarket.ts (if publishing)
|
||||
await relayHub.publishEvent(event)
|
||||
|
||||
// In any other component
|
||||
await relayHub.publishEvent(event)
|
||||
```
|
||||
|
||||
**Relay Hub Publishing Logic**:
|
||||
```typescript
|
||||
async publishEvent(event: Event): Promise<{ success: number; total: number }> {
|
||||
if (!this._isConnected) {
|
||||
throw new Error('Not connected to any relays')
|
||||
}
|
||||
|
||||
const relayUrls = Array.from(this.connectedRelays.keys())
|
||||
const results = await Promise.allSettled(
|
||||
relayUrls.map(relay => this.pool.publish([relay], event))
|
||||
)
|
||||
|
||||
const successful = results.filter(result => result.status === 'fulfilled').length
|
||||
const total = results.length
|
||||
|
||||
this.emit('eventPublished', { eventId: event.id, success: successful, total })
|
||||
return { success: successful, total }
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Event Querying and Subscription
|
||||
|
||||
**Consistent Query Interface**:
|
||||
```typescript
|
||||
// One-time event queries
|
||||
const events = await relayHub.queryEvents(filters)
|
||||
|
||||
// Real-time subscriptions
|
||||
const unsubscribe = relayHub.subscribe({
|
||||
id: 'subscription-id',
|
||||
filters: [{ kinds: [1], limit: 50 }],
|
||||
onEvent: (event) => {
|
||||
// Handle incoming event
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Shared Dependencies
|
||||
|
||||
**nostr-tools Package**:
|
||||
- `finalizeEvent` - Event signing and finalization
|
||||
- `nip04` - Encrypted message handling
|
||||
- `SimplePool` - Relay connection management
|
||||
- `EventTemplate` - Event structure typing
|
||||
|
||||
**Common Event Patterns**:
|
||||
- **Text Notes** (kind 1): Community posts and announcements
|
||||
- **Encrypted DMs** (kind 4): Private chat messages
|
||||
- **Market Events** (kinds 30017-30019): Stall, product, and market data
|
||||
- **Reactions** (kind 7): Emoji reactions to posts
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Environment Variables and Configuration
|
||||
|
||||
**Centralized Config** (`src/lib/config/index.ts`):
|
||||
```typescript
|
||||
interface NostrConfig {
|
||||
relays: string[]
|
||||
adminPubkeys: string[]
|
||||
}
|
||||
|
||||
interface MarketConfig {
|
||||
defaultNaddr: string
|
||||
supportedRelays: string[]
|
||||
lightningEnabled: boolean
|
||||
defaultCurrency: string
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
VITE_NOSTR_RELAYS: JSON array of relay URLs
|
||||
VITE_ADMIN_PUBKEYS: JSON array of admin public keys
|
||||
VITE_MARKET_RELAYS: JSON array of market-specific relays
|
||||
VITE_LIGHTNING_ENABLED: Boolean for Lightning integration
|
||||
```
|
||||
|
||||
**Default Relay Configuration**:
|
||||
```typescript
|
||||
// Fallback relay configuration
|
||||
supportedRelays: [
|
||||
'ws://127.0.0.1:7777',
|
||||
'wss://relay.damus.io',
|
||||
'wss://relay.snort.social',
|
||||
'wss://nostr-pub.wellorder.net',
|
||||
'wss://nostr.zebedee.cloud',
|
||||
'wss://nostr.walletofsatoshi.com'
|
||||
]
|
||||
```
|
||||
|
||||
### Configuration Utilities
|
||||
|
||||
**Admin Pubkey Validation**:
|
||||
```typescript
|
||||
export const configUtils = {
|
||||
isAdminPubkey(pubkey: string): boolean {
|
||||
return config.nostr.adminPubkeys.includes(pubkey)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling and Resilience
|
||||
|
||||
### Connection Management
|
||||
|
||||
**Automatic Reconnection**:
|
||||
```typescript
|
||||
// Relay hub automatically handles reconnection
|
||||
private scheduleReconnect(): void {
|
||||
if (this.reconnectInterval) {
|
||||
clearTimeout(this.reconnectInterval)
|
||||
}
|
||||
|
||||
this.reconnectInterval = setTimeout(async () => {
|
||||
await this.connect()
|
||||
}, this.reconnectDelay)
|
||||
}
|
||||
```
|
||||
|
||||
**Health Monitoring**:
|
||||
```typescript
|
||||
// Continuous health checks
|
||||
private startHealthCheck(): void {
|
||||
this.healthCheckInterval = setInterval(() => {
|
||||
this.performHealthCheck()
|
||||
}, this.healthCheckIntervalMs)
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling Patterns
|
||||
|
||||
**Graceful Degradation**:
|
||||
```typescript
|
||||
// Components handle connection failures gracefully
|
||||
async function loadNotes() {
|
||||
try {
|
||||
if (!relayHub.isConnected.value) {
|
||||
await relayHub.connect()
|
||||
}
|
||||
// ... load notes
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err : new Error('Failed to load notes')
|
||||
console.error('Failed to load notes:', err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Retry Logic**:
|
||||
```typescript
|
||||
// Market app retry pattern
|
||||
async def wait_for_nostr_events(nostr_client: NostrClient):
|
||||
while True:
|
||||
try:
|
||||
await subscribe_to_all_merchants()
|
||||
# ... process events
|
||||
except Exception as e:
|
||||
logger.warning(f"Subscription failed. Will retry in one minute: {e}")
|
||||
await asyncio.sleep(10)
|
||||
```
|
||||
|
||||
## Event Processing and Filtering
|
||||
|
||||
### Filter Management
|
||||
|
||||
**Dynamic Filter Creation**:
|
||||
```typescript
|
||||
// Feed component creates filters based on type
|
||||
const filters: any[] = [{
|
||||
kinds: [1], // TEXT_NOTE
|
||||
limit: 50
|
||||
}]
|
||||
|
||||
// Filter by authors for announcements
|
||||
if (props.feedType === 'announcements' && hasAdminPubkeys.value) {
|
||||
filters[0].authors = adminPubkeys
|
||||
}
|
||||
```
|
||||
|
||||
**Market Event Filtering**:
|
||||
```typescript
|
||||
// Market-specific filters
|
||||
const _filters_for_stall_events = (public_keys: List[str], since: int) -> List:
|
||||
stall_filter = {"kinds": [30017], "authors": public_keys}
|
||||
if since and since != 0:
|
||||
stall_filter["since"] = since
|
||||
return [stall_filter]
|
||||
```
|
||||
|
||||
### Event Processing Pipeline
|
||||
|
||||
**Event Processing Flow**:
|
||||
```typescript
|
||||
// 1. Receive event from relay
|
||||
onEvent: (event) => {
|
||||
// 2. Process and transform
|
||||
const newNote = {
|
||||
id: event.id,
|
||||
pubkey: event.pubkey,
|
||||
content: event.content,
|
||||
created_at: event.created_at,
|
||||
tags: event.tags || [],
|
||||
mentions: event.tags?.filter(tag => tag[0] === 'p').map(tag => tag[1]) || [],
|
||||
isReply: event.tags?.some(tag => tag[0] === 'e' && tag[3] === 'reply'),
|
||||
replyTo: event.tags?.find(tag => tag[0] === 'e' && tag[3] === 'reply')?.[1]
|
||||
}
|
||||
|
||||
// 3. Apply business logic filters
|
||||
let shouldInclude = true
|
||||
if (props.feedType === 'announcements' && !isAdminPost(event.pubkey)) {
|
||||
shouldInclude = false
|
||||
}
|
||||
|
||||
// 4. Add to state if approved
|
||||
if (shouldInclude) {
|
||||
notes.value.unshift(newNote)
|
||||
// 5. Limit array size for memory management
|
||||
if (notes.value.length > 100) {
|
||||
notes.value = notes.value.slice(0, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Memory Management
|
||||
|
||||
**Array Size Limits**:
|
||||
```typescript
|
||||
// Prevent memory issues with large feeds
|
||||
if (notes.value.length > 100) {
|
||||
notes.value = notes.value.slice(0, 100)
|
||||
}
|
||||
```
|
||||
|
||||
**Subscription Cleanup**:
|
||||
```typescript
|
||||
// Proper cleanup prevents memory leaks
|
||||
onUnmounted(() => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe()
|
||||
unsubscribe = null
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Connection Pooling
|
||||
|
||||
**Shared WebSocket Connections**:
|
||||
```typescript
|
||||
// Single pool shared across all components
|
||||
private pool: SimplePool
|
||||
|
||||
// Components don't create individual connections
|
||||
// They all use the centralized relay hub
|
||||
```
|
||||
|
||||
**Efficient Relay Usage**:
|
||||
```typescript
|
||||
// Query multiple relays simultaneously
|
||||
const events = await this.pool.querySync(availableRelays, filter)
|
||||
|
||||
// Publish to all relays in parallel
|
||||
const results = await Promise.allSettled(
|
||||
relayUrls.map(relay => this.pool.publish([relay], event))
|
||||
)
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Encryption and Privacy
|
||||
|
||||
**End-to-End Encryption**:
|
||||
```typescript
|
||||
// All chat messages are encrypted using nip04
|
||||
const encryptedContent = await nip04.encrypt(
|
||||
privateKey,
|
||||
publicKey,
|
||||
content
|
||||
)
|
||||
```
|
||||
|
||||
**Private Key Management**:
|
||||
```typescript
|
||||
// Keys never leave the browser
|
||||
// Validation of key format and length
|
||||
if (privateKey.length !== 64) {
|
||||
throw new Error(`Invalid private key length: ${privateKey.length} (expected 64)`)
|
||||
}
|
||||
|
||||
// Hex format validation
|
||||
const hexRegex = /^[0-9a-fA-F]+$/
|
||||
if (!hexRegex.test(privateKey)) {
|
||||
throw new Error(`Invalid private key format: contains non-hex characters`)
|
||||
}
|
||||
```
|
||||
|
||||
### Access Control
|
||||
|
||||
**Admin Pubkey Validation**:
|
||||
```typescript
|
||||
// Only admin pubkeys can post announcements
|
||||
function isAdminPost(pubkey: string): boolean {
|
||||
return configUtils.isAdminPubkey(pubkey)
|
||||
}
|
||||
|
||||
// Filter content based on user permissions
|
||||
if (props.feedType === 'announcements' && !isAdminPost(event.pubkey)) {
|
||||
shouldInclude = false
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits of This Architecture
|
||||
|
||||
### 1. **Consistent Event Handling**
|
||||
- All components use the same event creation and publishing patterns
|
||||
- Centralized relay management ensures consistent behavior
|
||||
- Shared error handling and retry logic
|
||||
|
||||
### 2. **Efficient Resource Usage**
|
||||
- Single WebSocket connection pool shared across all components
|
||||
- No duplicate relay connections
|
||||
- Centralized connection health monitoring
|
||||
|
||||
### 3. **Simplified Component Logic**
|
||||
- Components focus on business logic, not relay management
|
||||
- Consistent API for all Nostr operations
|
||||
- Easy to add new event types and functionality
|
||||
|
||||
### 4. **Maintainable Codebase**
|
||||
- Single source of truth for relay connections
|
||||
- Centralized event publishing logic
|
||||
- Easy to debug and monitor Nostr operations
|
||||
|
||||
### 5. **Robust Error Handling**
|
||||
- Automatic reconnection and health monitoring
|
||||
- Graceful degradation when relays are unavailable
|
||||
- Comprehensive error logging and user feedback
|
||||
|
||||
### 6. **Performance Optimized**
|
||||
- Memory management with array size limits
|
||||
- Efficient connection pooling
|
||||
- Parallel relay operations
|
||||
|
||||
## Future Extensibility
|
||||
|
||||
This architecture makes it easy to add new Nostr functionality:
|
||||
|
||||
1. **New Event Types**: Add new event creation utilities following the existing pattern
|
||||
2. **Additional Relays**: Configure new relays in the centralized relay hub
|
||||
3. **New Components**: Import the existing singletons and use the established patterns
|
||||
4. **Enhanced Features**: Extend the relay hub with new capabilities (e.g., event caching, filtering)
|
||||
5. **Advanced Filtering**: Implement more sophisticated event filtering and processing
|
||||
6. **Caching Layer**: Add event caching for improved performance
|
||||
7. **Rate Limiting**: Implement rate limiting for relay operations
|
||||
8. **Metrics and Monitoring**: Add comprehensive metrics for relay performance
|
||||
|
||||
This architecture makes the app more maintainable, performant, and user-friendly while providing a solid foundation for future features.
|
||||
Loading…
Add table
Add a link
Reference in a new issue