- Introduced comprehensive documentation for the new Wallet Module, detailing its purpose, features, and key components. - Added WebSocket integration documentation, outlining real-time balance updates, connection management, and error handling. - Updated README and module index files to include references to the Wallet Module, enhancing overall module visibility and accessibility. These changes improve the clarity and usability of the Wallet Module, providing developers with essential information for implementation and integration.
603 lines
No EOL
20 KiB
Markdown
603 lines
No EOL
20 KiB
Markdown
# ⚙️ Core Services Overview
|
|
|
|
> **Shared infrastructure services** providing foundational functionality across all modules with reactive architecture, dependency injection, and lifecycle management.
|
|
|
|
## Table of Contents
|
|
|
|
- [[#Service Architecture]]
|
|
- [[#Available Services]]
|
|
- [[#Service Lifecycle]]
|
|
- [[#Dependency Injection]]
|
|
- [[#Service Development]]
|
|
- [[#Testing Services]]
|
|
|
|
## Service Architecture
|
|
|
|
### **BaseService Foundation**
|
|
All core services extend the `BaseService` abstract class which provides:
|
|
|
|
- **Reactive State Management** - Integration with Vue's reactivity system
|
|
- **Lifecycle Management** - Standardized initialization and disposal
|
|
- **Error Handling** - Consistent error patterns across services
|
|
- **Type Safety** - Full TypeScript support with strict typing
|
|
|
|
```typescript
|
|
abstract class BaseService {
|
|
protected isInitialized = ref(false)
|
|
protected isDisposed = ref(false)
|
|
|
|
abstract initialize(): Promise<void>
|
|
abstract dispose(): Promise<void>
|
|
|
|
// Reactive state helpers
|
|
protected createReactiveState<T>(initialValue: T): Ref<T>
|
|
protected createComputedState<T>(getter: () => T): ComputedRef<T>
|
|
}
|
|
```
|
|
|
|
### **Service Registration Pattern**
|
|
Services are registered in the dependency injection container during module installation:
|
|
|
|
```typescript
|
|
// Service registration (in base module)
|
|
container.provide(SERVICE_TOKENS.RELAY_HUB, relayHub)
|
|
container.provide(SERVICE_TOKENS.AUTH_SERVICE, authService)
|
|
container.provide(SERVICE_TOKENS.STORAGE_SERVICE, storageService)
|
|
|
|
// Service consumption (anywhere in app)
|
|
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
|
const auth = injectService(SERVICE_TOKENS.AUTH_SERVICE)
|
|
```
|
|
|
|
## Available Services
|
|
|
|
### **AuthService** 🔐
|
|
**Purpose:** User authentication and identity management
|
|
**Location:** `src/modules/base/auth/auth-service.ts`
|
|
**Token:** `SERVICE_TOKENS.AUTH_SERVICE`
|
|
|
|
**Key Features:**
|
|
- **Key Management** - Secure generation, import, and storage of Nostr keys
|
|
- **User Sessions** - Persistent authentication with encrypted storage
|
|
- **Profile Management** - User profile creation and updates
|
|
- **Security** - Client-side key handling with no server storage
|
|
|
|
**Reactive State:**
|
|
```typescript
|
|
interface AuthService {
|
|
user: Ref<NostrUser | null> // Current authenticated user
|
|
isAuthenticated: ComputedRef<boolean> // Authentication status
|
|
isLoading: Ref<boolean> // Loading state
|
|
loginError: Ref<string | null> // Login error message
|
|
}
|
|
```
|
|
|
|
**Key Methods:**
|
|
- `generateKeyPair()` - Create new Nostr key pair
|
|
- `loginWithPrivateKey(privateKey: string)` - Authenticate with existing key
|
|
- `logout()` - Clear session and user data
|
|
- `updateProfile(profile: UserMetadata)` - Update user profile
|
|
|
|
**See:** [[authentication|📖 Authentication Service Documentation]]
|
|
|
|
### **PaymentService** 💳
|
|
**Purpose:** Centralized Lightning payment processing and wallet management
|
|
**Location:** `src/core/services/PaymentService.ts`
|
|
**Token:** `SERVICE_TOKENS.PAYMENT_SERVICE`
|
|
|
|
**Key Features:**
|
|
- **Wallet Management** - Multi-wallet support with preferred wallet selection
|
|
- **Payment Processing** - Lightning invoices, QR code generation, and external wallet handling
|
|
- **Balance Management** - Real-time wallet balance updates and tracking
|
|
- **Payment Validation** - Balance checking and payment amount validation
|
|
- **Error Handling** - Consistent payment error handling and user feedback
|
|
|
|
**Reactive State:**
|
|
```typescript
|
|
interface PaymentService {
|
|
isProcessingPayment: ComputedRef<boolean> // Payment in progress
|
|
paymentError: ComputedRef<string | null> // Payment error state
|
|
totalBalance: number // Total balance across wallets (getter)
|
|
hasWalletWithBalance: boolean // Wallet availability check (getter)
|
|
}
|
|
```
|
|
|
|
**Key Methods:**
|
|
- `payWithWallet(paymentRequest, amount?, options?)` - Process Lightning payment
|
|
- `generateQRCode(paymentRequest, options?)` - Generate payment QR codes
|
|
- `openExternalWallet(paymentRequest)` - Launch external Lightning wallet
|
|
- `updateWalletBalance(balanceMsat, walletId?)` - Update wallet balance from WebSocket
|
|
- `getPreferredWallet()` - Get the primary wallet for operations
|
|
- `getWalletWithBalance(requiredAmount?)` - Find wallet with sufficient balance
|
|
|
|
**Wallet Selection Logic:**
|
|
- Always uses first wallet (`wallets[0]`) for consistency across modules
|
|
- Provides helper methods for wallet selection and balance checking
|
|
- Integrates with AuthService for wallet credentials and management
|
|
|
|
**See:** [[payment-service|📖 Payment Service Documentation]]
|
|
|
|
### **RelayHub** 🌐
|
|
**Purpose:** Centralized Nostr relay connection management
|
|
**Location:** `src/modules/base/nostr/relay-hub.ts`
|
|
**Token:** `SERVICE_TOKENS.RELAY_HUB`
|
|
|
|
**Key Features:**
|
|
- **Connection Management** - Automatic connection, reconnection, and failover
|
|
- **Event Publishing** - Reliable event publishing across multiple relays
|
|
- **Subscription Management** - Efficient event subscriptions with deduplication
|
|
- **Performance Monitoring** - Relay latency and success rate tracking
|
|
|
|
**Reactive State:**
|
|
```typescript
|
|
interface RelayHub {
|
|
connectedRelays: Ref<string[]> // Currently connected relays
|
|
connectionStatus: ComputedRef<ConnectionStatus> // Overall connection status
|
|
relayStats: Ref<Map<string, RelayStats>> // Per-relay statistics
|
|
isConnecting: Ref<boolean> // Connection in progress
|
|
}
|
|
```
|
|
|
|
**Key Methods:**
|
|
- `connect(relays: string[])` - Connect to relay URLs
|
|
- `publishEvent(event: NostrEvent)` - Publish event to all connected relays
|
|
- `subscribe(filters: Filter[], callback: EventCallback)` - Subscribe to events
|
|
- `getRelayInfo(url: string)` - Get relay connection information
|
|
|
|
**See:** [[../01-architecture/relay-hub|📖 Relay Hub Architecture Documentation]]
|
|
|
|
### **StorageService** 💾
|
|
**Purpose:** User-scoped local storage operations
|
|
**Location:** `src/core/services/StorageService.ts`
|
|
**Token:** `SERVICE_TOKENS.STORAGE_SERVICE`
|
|
|
|
**Key Features:**
|
|
- **User-Scoped Storage** - Automatic key prefixing per authenticated user
|
|
- **Type-Safe Operations** - Strongly typed get/set operations with JSON serialization
|
|
- **Reactive Updates** - Optional reactive storage with Vue refs
|
|
- **Migration Support** - Data migration between storage schema versions
|
|
|
|
**Key Methods:**
|
|
```typescript
|
|
interface StorageService {
|
|
setUserData<T>(key: string, data: T): void
|
|
getUserData<T>(key: string, defaultValue?: T): T | undefined
|
|
removeUserData(key: string): void
|
|
clearUserData(): void
|
|
|
|
// Reactive variants
|
|
getReactiveUserData<T>(key: string, defaultValue: T): Ref<T>
|
|
setReactiveUserData<T>(key: string, ref: Ref<T>): void
|
|
}
|
|
```
|
|
|
|
**Storage Patterns:**
|
|
- User-specific keys: `user:{pubkey}:settings`
|
|
- Global application keys: `app:theme`
|
|
- Module-specific keys: `user:{pubkey}:chat:contacts`
|
|
|
|
**See:** [[storage-service|📖 Storage Service Documentation]]
|
|
|
|
### **ToastService** 📢
|
|
**Purpose:** Application-wide notifications and user feedback
|
|
**Location:** `src/core/services/ToastService.ts`
|
|
**Token:** `SERVICE_TOKENS.TOAST_SERVICE`
|
|
|
|
**Key Features:**
|
|
- **Context-Specific Methods** - Pre-configured toasts for common scenarios
|
|
- **Consistent Messaging** - Standardized success, error, and info messages
|
|
- **Accessibility** - Screen reader compatible notifications
|
|
- **Customizable** - Support for custom toast content and actions
|
|
|
|
**Organized by Context:**
|
|
```typescript
|
|
interface ToastService {
|
|
// Authentication context
|
|
auth: {
|
|
loginSuccess(): void
|
|
loginError(error?: string): void
|
|
logoutSuccess(): void
|
|
keyGenerated(): void
|
|
}
|
|
|
|
// Payment context
|
|
payment: {
|
|
invoiceCreated(): void
|
|
paymentReceived(): void
|
|
paymentFailed(error?: string): void
|
|
}
|
|
|
|
// Clipboard operations
|
|
clipboard: {
|
|
copied(item?: string): void
|
|
copyFailed(): void
|
|
}
|
|
|
|
// General operations
|
|
operation: {
|
|
success(message: string): void
|
|
error(message: string): void
|
|
info(message: string): void
|
|
}
|
|
}
|
|
```
|
|
|
|
**See:** [[toast-service|📖 Toast Service Documentation]]
|
|
|
|
### **EventBus** 📡
|
|
**Purpose:** Inter-module communication and event coordination
|
|
**Location:** `src/core/services/EventBus.ts`
|
|
**Token:** `SERVICE_TOKENS.EVENT_BUS`
|
|
|
|
**Key Features:**
|
|
- **Type-Safe Events** - Strongly typed event payloads
|
|
- **Module Isolation** - Clean communication between modules
|
|
- **Event Namespacing** - Organized event names by domain
|
|
- **Subscription Management** - Easy subscribe/unsubscribe patterns
|
|
|
|
**Event Categories:**
|
|
```typescript
|
|
interface EventBusEvents {
|
|
// User events
|
|
'user:authenticated': { userId: string, profile: UserMetadata }
|
|
'user:profile-updated': { userId: string, changes: Partial<UserMetadata> }
|
|
'user:logout': { userId: string }
|
|
|
|
// Chat events
|
|
'chat:message-received': { messageId: string, from: string, content: string }
|
|
'chat:typing-start': { from: string, chatId: string }
|
|
|
|
// Payment events
|
|
'payment:invoice-created': { invoiceId: string, amount: number }
|
|
'payment:received': { invoiceId: string, amount: number }
|
|
|
|
// Relay events
|
|
'relay:connected': { url: string }
|
|
'relay:disconnected': { url: string, reason?: string }
|
|
}
|
|
```
|
|
|
|
**See:** [[../01-architecture/event-bus|📖 Event Bus Communication Documentation]]
|
|
|
|
### **WalletService** 🏦
|
|
**Purpose:** Wallet operations and transaction management
|
|
**Location:** `src/modules/wallet/services/WalletService.ts`
|
|
**Token:** `SERVICE_TOKENS.WALLET_SERVICE`
|
|
|
|
**Key Features:**
|
|
- **Payment Operations** - Send Lightning payments to invoices, addresses, and LNURL
|
|
- **Payment Link Management** - Create and manage LNURL payment links and Lightning addresses
|
|
- **Transaction History** - Load and manage comprehensive transaction history
|
|
- **Real-time Updates** - Add new transactions from WebSocket notifications
|
|
|
|
**Reactive State:**
|
|
```typescript
|
|
interface WalletService {
|
|
transactions: ComputedRef<PaymentTransaction[]> // Transaction history
|
|
payLinks: ComputedRef<PayLink[]> // Created payment links
|
|
isCreatingPayLink: ComputedRef<boolean> // Pay link creation state
|
|
isSendingPayment: ComputedRef<boolean> // Payment sending state
|
|
error: ComputedRef<string | null> // Error state
|
|
}
|
|
```
|
|
|
|
**Key Methods:**
|
|
- `sendPayment(request: SendPaymentRequest)` - Send Lightning payment
|
|
- `createReceiveAddress(params)` - Create LNURL payment link with Lightning address
|
|
- `deletePayLink(linkId: string)` - Remove payment link
|
|
- `addTransaction(payment)` - Add transaction from WebSocket notification
|
|
- `refresh()` - Reload transactions and payment links
|
|
|
|
### **WalletWebSocketService** ⚡
|
|
**Purpose:** Real-time wallet balance updates via WebSocket
|
|
**Location:** `src/modules/wallet/services/WalletWebSocketService.ts`
|
|
**Token:** `SERVICE_TOKENS.WALLET_WEBSOCKET_SERVICE`
|
|
|
|
**Key Features:**
|
|
- **Real-time Connection** - WebSocket connection to LNbits for instant updates
|
|
- **Balance Synchronization** - Smart balance updates with unit conversion handling
|
|
- **Payment Notifications** - Toast notifications for incoming payments
|
|
- **Connection Management** - Automatic reconnection with exponential backoff
|
|
- **Battery Optimization** - VisibilityService integration for mobile efficiency
|
|
|
|
**Reactive State:**
|
|
```typescript
|
|
interface WalletWebSocketService {
|
|
isConnected: Ref<boolean> // WebSocket connection status
|
|
connectionStatus: Ref<string> // Connection state details
|
|
}
|
|
```
|
|
|
|
**Connection States:**
|
|
- `disconnected` - Not connected
|
|
- `connecting` - Connection in progress
|
|
- `connected` - Successfully connected
|
|
- `reconnecting` - Attempting to reconnect
|
|
- `error` - Connection failed
|
|
- `failed` - Max reconnection attempts reached
|
|
|
|
**Key Methods:**
|
|
- `reconnect()` - Manual reconnection trigger
|
|
- `disconnect()` - Graceful disconnection
|
|
- `cleanup()` - Service disposal
|
|
|
|
**WebSocket Integration:**
|
|
- Connects to `wss://lnbits-server/api/v1/ws/{walletInkey}`
|
|
- Handles incoming/outgoing payment balance differences
|
|
- Updates PaymentService with converted balance (sats → millisats)
|
|
- Integrates with WalletService for transaction updates
|
|
|
|
## Service Lifecycle
|
|
|
|
### **Initialization Phase**
|
|
Services are initialized in dependency order during application startup:
|
|
|
|
```typescript
|
|
// 1. Base services (no dependencies)
|
|
await authService.initialize()
|
|
await storageService.initialize()
|
|
|
|
// 2. Infrastructure services (depend on base services)
|
|
await relayHub.initialize()
|
|
await toastService.initialize()
|
|
|
|
// 3. Feature services (depend on infrastructure)
|
|
await chatService.initialize()
|
|
await eventsService.initialize()
|
|
```
|
|
|
|
### **Service Dependencies**
|
|
Services declare their dependencies through constructor injection:
|
|
|
|
```typescript
|
|
class ChatService extends BaseService {
|
|
constructor(
|
|
private auth = injectService(SERVICE_TOKENS.AUTH_SERVICE),
|
|
private relayHub = injectService(SERVICE_TOKENS.RELAY_HUB),
|
|
private storage = injectService(SERVICE_TOKENS.STORAGE_SERVICE),
|
|
private eventBus = injectService(SERVICE_TOKENS.EVENT_BUS)
|
|
) {
|
|
super()
|
|
}
|
|
}
|
|
```
|
|
|
|
### **Disposal Phase**
|
|
Services are disposed in reverse dependency order during application shutdown:
|
|
|
|
```typescript
|
|
async dispose(): Promise<void> {
|
|
// Clean up subscriptions
|
|
this.subscriptions.forEach(sub => sub.close())
|
|
|
|
// Save persistent state
|
|
await this.storage.setUserData('chat:messages', this.messages.value)
|
|
|
|
// Mark as disposed
|
|
this.isDisposed.value = true
|
|
}
|
|
```
|
|
|
|
## Dependency Injection
|
|
|
|
### **Service Tokens**
|
|
Type-safe service tokens prevent runtime errors and enable proper TypeScript inference:
|
|
|
|
```typescript
|
|
export const SERVICE_TOKENS = {
|
|
AUTH_SERVICE: Symbol('AUTH_SERVICE') as InjectionKey<AuthService>,
|
|
RELAY_HUB: Symbol('RELAY_HUB') as InjectionKey<RelayHub>,
|
|
STORAGE_SERVICE: Symbol('STORAGE_SERVICE') as InjectionKey<StorageService>,
|
|
TOAST_SERVICE: Symbol('TOAST_SERVICE') as InjectionKey<ToastService>,
|
|
} as const
|
|
```
|
|
|
|
### **Service Registration**
|
|
Services are registered during module installation:
|
|
|
|
```typescript
|
|
// In base module installation
|
|
export async function installBaseModule(app: App) {
|
|
// Create service instances
|
|
const authService = new AuthService()
|
|
const relayHub = new RelayHub()
|
|
const storageService = new StorageService()
|
|
|
|
// Register in container
|
|
container.provide(SERVICE_TOKENS.AUTH_SERVICE, authService)
|
|
container.provide(SERVICE_TOKENS.RELAY_HUB, relayHub)
|
|
container.provide(SERVICE_TOKENS.STORAGE_SERVICE, storageService)
|
|
|
|
// Initialize services
|
|
await authService.initialize()
|
|
await relayHub.initialize()
|
|
}
|
|
```
|
|
|
|
### **Service Consumption**
|
|
Services are injected where needed using type-safe injection:
|
|
|
|
```typescript
|
|
// In composables
|
|
export function useAuth() {
|
|
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE)
|
|
|
|
return {
|
|
user: authService.user,
|
|
login: authService.login,
|
|
logout: authService.logout
|
|
}
|
|
}
|
|
|
|
// In components
|
|
<script setup>
|
|
const toast = injectService(SERVICE_TOKENS.TOAST_SERVICE)
|
|
const handleSuccess = () => toast.operation.success('Action completed!')
|
|
</script>
|
|
```
|
|
|
|
## Service Development
|
|
|
|
### **Creating a New Service**
|
|
|
|
#### 1. Service Class Implementation
|
|
```typescript
|
|
// src/core/services/MyService.ts
|
|
export class MyService extends BaseService {
|
|
// Reactive state
|
|
private readonly _data = ref<MyData[]>([])
|
|
private readonly _isLoading = ref(false)
|
|
|
|
// Public readonly access to state
|
|
public readonly data = readonly(this._data)
|
|
public readonly isLoading = readonly(this._isLoading)
|
|
|
|
constructor(
|
|
private dependency = injectService(SERVICE_TOKENS.DEPENDENCY)
|
|
) {
|
|
super()
|
|
}
|
|
|
|
async initialize(): Promise<void> {
|
|
// Initialization logic
|
|
await this.loadInitialData()
|
|
this.isInitialized.value = true
|
|
}
|
|
|
|
async dispose(): Promise<void> {
|
|
// Cleanup logic
|
|
this._data.value = []
|
|
this.isDisposed.value = true
|
|
}
|
|
|
|
// Public methods
|
|
async createItem(item: CreateItemRequest): Promise<MyData> {
|
|
this._isLoading.value = true
|
|
try {
|
|
const newItem = await this.dependency.create(item)
|
|
this._data.value.push(newItem)
|
|
return newItem
|
|
} finally {
|
|
this._isLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2. Service Token Registration
|
|
```typescript
|
|
// Add to SERVICE_TOKENS
|
|
export const SERVICE_TOKENS = {
|
|
// ... existing tokens
|
|
MY_SERVICE: Symbol('MY_SERVICE') as InjectionKey<MyService>,
|
|
} as const
|
|
```
|
|
|
|
#### 3. Service Registration in Module
|
|
```typescript
|
|
// In module installation
|
|
const myService = new MyService()
|
|
container.provide(SERVICE_TOKENS.MY_SERVICE, myService)
|
|
await myService.initialize()
|
|
```
|
|
|
|
### **Service Best Practices**
|
|
|
|
#### **Reactive State Management**
|
|
- Use `ref()` for mutable state, `readonly()` for public access
|
|
- Provide computed properties for derived state
|
|
- Use `watch()` and `watchEffect()` for side effects
|
|
|
|
#### **Error Handling**
|
|
- Throw descriptive errors with proper types
|
|
- Use try/catch blocks with proper cleanup
|
|
- Log errors appropriately for debugging
|
|
|
|
#### **Performance Optimization**
|
|
- Implement proper subscription cleanup in `dispose()`
|
|
- Use debouncing for frequent operations
|
|
- Cache expensive computations with `computed()`
|
|
|
|
## Testing Services
|
|
|
|
### **Service Unit Tests**
|
|
```typescript
|
|
// tests/unit/services/MyService.test.ts
|
|
describe('MyService', () => {
|
|
let service: MyService
|
|
let mockDependency: MockType<DependencyService>
|
|
|
|
beforeEach(() => {
|
|
// Setup mocks
|
|
mockDependency = createMockService()
|
|
|
|
// Create service with mocks
|
|
service = new MyService(mockDependency)
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await service.dispose()
|
|
})
|
|
|
|
it('should initialize correctly', async () => {
|
|
await service.initialize()
|
|
|
|
expect(service.isInitialized.value).toBe(true)
|
|
expect(service.data.value).toEqual([])
|
|
})
|
|
|
|
it('should create items', async () => {
|
|
await service.initialize()
|
|
|
|
const item = await service.createItem({ name: 'test' })
|
|
|
|
expect(service.data.value).toContain(item)
|
|
expect(mockDependency.create).toHaveBeenCalledWith({ name: 'test' })
|
|
})
|
|
})
|
|
```
|
|
|
|
### **Integration Tests**
|
|
```typescript
|
|
// tests/integration/services/ServiceIntegration.test.ts
|
|
describe('Service Integration', () => {
|
|
let container: DIContainer
|
|
|
|
beforeEach(async () => {
|
|
container = createTestContainer()
|
|
await installTestServices(container)
|
|
})
|
|
|
|
it('should handle cross-service communication', async () => {
|
|
const authService = container.get(SERVICE_TOKENS.AUTH_SERVICE)
|
|
const chatService = container.get(SERVICE_TOKENS.CHAT_SERVICE)
|
|
|
|
await authService.login('test-key')
|
|
const message = await chatService.sendMessage('Hello')
|
|
|
|
expect(message.author).toBe(authService.user.value?.pubkey)
|
|
})
|
|
})
|
|
```
|
|
|
|
## See Also
|
|
|
|
### Service Documentation
|
|
- **[[authentication|🔐 Authentication Service]]** - User identity and session management
|
|
- **[[payment-service|💳 Payment Service]]** - Lightning payment processing and wallet management
|
|
- **[[storage-service|💾 Storage Service]]** - User-scoped data persistence
|
|
- **[[toast-service|📢 Toast Service]]** - User notifications and feedback
|
|
- **[[visibility-service|👁️ Visibility Service]]** - Dynamic UI component control
|
|
- **[[../02-modules/wallet-module/index|💰 Wallet Services]]** - WalletService and WebSocket integration
|
|
|
|
### Architecture References
|
|
- **[[../01-architecture/dependency-injection|⚙️ Dependency Injection]]** - DI container system
|
|
- **[[../01-architecture/event-bus|📡 Event Bus Communication]]** - Inter-service messaging
|
|
- **[[../02-modules/index|📦 Module System]]** - How services integrate with modules
|
|
- **[[../04-development/testing|🧪 Testing Guide]]** - Service testing patterns
|
|
|
|
---
|
|
|
|
**Tags:** #services #architecture #dependency-injection #reactive-state
|
|
**Last Updated:** 2025-09-06
|
|
**Author:** Development Team |