- 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.
17 KiB
⚡ WebSocket Integration
Real-time wallet balance updates through LNbits WebSocket API with automatic reconnection, battery optimization, and smart unit conversion handling.
Table of Contents
- #Overview
- #Architecture
- #Connection Management
- #Message Processing
- #Balance Update Logic
- #Error Handling
- #Battery Optimization
- #Configuration
- #Troubleshooting
Overview
The Wallet WebSocket integration provides real-time synchronization between the Ario wallet interface and the LNbits Lightning backend. This enables instant balance updates and payment notifications without requiring page refreshes or manual polling.
Key Benefits
- Instant UI Updates - Balance changes appear immediately when payments are sent or received
- Payment Notifications - Toast notifications for incoming payments
- Battery Efficient - Pauses when app is not visible to save mobile battery
- Reliable Connection - Automatic reconnection with exponential backoff
- Smart Conversion - Handles LNbits sats/millisats behavior differences
WebSocket Endpoint
wss://your-lnbits-server/api/v1/ws/{walletInkey}
Where walletInkey is the invoice/readonly key for the wallet.
Architecture
Service Integration
┌─ WalletWebSocketService ─┐ ┌─ PaymentService ─┐ ┌─ UI Components ─┐
│ │ │ │ │ │
│ • WebSocket Connection │───▶│ • Balance Update │───▶│ • Balance Display│
│ • Message Processing │ │ • Wallet Mgmt │ │ • Toast Notifications
│ • Reconnection Logic │ │ • Multi-wallet │ │ • Transaction List │
│ • Battery Optimization │ │ │ │ │
└──────────────────────────┘ └──────────────────┘ └──────────────────┘
│ │
│ │
▼ ▼
┌─ VisibilityService ─────┐ ┌─ AuthService ──────┐
│ │ │ │
│ • App Visibility │ │ • User Wallets │
│ • Connection Pausing │ │ • Authentication │
│ • Resume Management │ │ • Wallet Selection │
└─────────────────────────┘ └────────────────────┘
Dependency Graph
WalletWebSocketService
├── BaseService (lifecycle management)
├── PaymentService (balance updates)
├── WalletService (transaction management)
├── AuthService (wallet credentials)
└── VisibilityService (battery optimization)
Initialization Flow
1. Module Installation
├── Create WalletWebSocketService instance
├── Register in DI container
└── Initialize with dependencies
2. Service Initialization
├── Wait for dependencies (AuthService, PaymentService)
├── Register with VisibilityService
├── Set up auth event listeners
└── Attempt initial connection
3. Authentication Events
├── auth:login → Connect WebSocket
└── auth:logout → Disconnect WebSocket
4. Connection Lifecycle
├── Connect → Update status → Listen for messages
├── Message → Process → Update balance
├── Disconnect → Attempt reconnection
└── Visibility Change → Pause/Resume
Connection Management
Connection Lifecycle
Initial Connection
// Triggered by authentication events
eventBus.on('auth:login', () => {
setTimeout(() => {
this.connectIfNeeded()
}, 500) // Small delay for auth processing
})
Connection Process
private async connect(walletInkey: string): Promise<void> {
// 1. Build WebSocket URL
const baseUrl = import.meta.env.VITE_LNBITS_BASE_URL
const wsProtocol = baseUrl.startsWith('https') ? 'wss:' : 'ws:'
const host = baseUrl.replace(/^https?:\/\//, '').replace(/\/$/, '')
const wsUrl = `${wsProtocol}//${host}/api/v1/ws/${walletInkey}`
// 2. Create WebSocket connection
this.ws = new WebSocket(wsUrl)
// 3. Set up event handlers
this.ws.onopen = this.handleOpen.bind(this)
this.ws.onmessage = this.handleMessage.bind(this)
this.ws.onclose = this.handleClose.bind(this)
this.ws.onerror = this.handleWebSocketError.bind(this)
}
Reconnection Strategy
Exponential Backoff
private scheduleReconnect(): void {
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
this.connectionStatus.value = 'failed'
return
}
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
const delay = this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts)
this.reconnectAttempts++
this.reconnectTimer = setTimeout(() => {
this.connectIfNeeded()
}, delay)
}
Connection Health Monitoring
// Connection states tracked
enum ConnectionStatus {
DISCONNECTED = 'disconnected',
CONNECTING = 'connecting',
CONNECTED = 'connected',
RECONNECTING = 'reconnecting',
ERROR = 'error',
FAILED = 'failed'
}
Graceful Disconnection
public disconnect(): void {
// Clear reconnection timer
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
// Close WebSocket with normal closure code
if (this.ws) {
this.ws.close(1000, 'Client disconnect')
this.ws = null
}
// Update reactive state
this.isConnected.value = false
this.connectionStatus.value = 'disconnected'
}
Message Processing
Message Structure
LNbits WebSocket sends messages in this format:
{
"wallet_balance": 1000820, // Balance in sats
"payment": { // Payment details (optional)
"amount": 100000, // Amount in millisats (positive = received, negative = sent)
"payment_hash": "abc123...",
"description": "Payment description",
"time": 1694123456,
"pending": false
}
}
Message Handler
private handleMessage(event: MessageEvent): void {
try {
const data = JSON.parse(event.data)
// Process payment notification first
if (data.payment) {
this.handlePaymentNotification(data.payment)
}
// Process balance update
if (data.wallet_balance !== undefined) {
this.processBalanceUpdate(data)
}
} catch (error) {
console.error('Failed to parse WebSocket message:', error)
}
}
Payment Notification Processing
private handlePaymentNotification(payment: any): void {
// Add to transaction history via WalletService
if (this.walletService?.addTransaction) {
this.walletService.addTransaction(payment)
}
// Show toast notification for incoming payments
if (payment.amount > 0 && !payment.pending) {
const amountSats = Math.abs(payment.amount / 1000)
this.toast.success(`Received ${amountSats} sats!`)
}
}
Balance Update Logic
LNbits WebSocket Behavior
LNbits has different behavior for incoming vs outgoing payments:
- Incoming payments: WebSocket sends post-payment balance (already includes received amount)
- Outgoing payments: WebSocket sends pre-payment balance (before deduction)
Smart Balance Conversion
private processBalanceUpdate(data: any): void {
let finalBalance = data.wallet_balance // Balance in sats from LNbits
if (data.payment) {
if (data.payment.amount < 0) {
// Outgoing payment - LNbits sends pre-payment balance
// We need to subtract the payment amount
const paymentSats = Math.abs(data.payment.amount) / 1000
finalBalance = data.wallet_balance - paymentSats
console.log('Outgoing payment adjustment:', {
originalBalance: data.wallet_balance,
paymentSats: paymentSats,
finalBalance: finalBalance
})
} else if (data.payment.amount > 0) {
// Incoming payment - LNbits sends post-payment balance
// Use balance as-is
console.log('Incoming payment - using balance as-is:', data.wallet_balance)
}
}
// Update balance via PaymentService
this.updateWalletBalance(finalBalance)
}
Balance Update Integration
private updateWalletBalance(balanceSats: number): void {
// Convert sats to millisats for internal storage
const balanceMsat = balanceSats * 1000
// Get connected wallet ID
const wallet = this.paymentService?.getPreferredWallet?.()
const walletId = wallet?.id
// Update via PaymentService for consistency
if (this.paymentService?.updateWalletBalance) {
this.paymentService.updateWalletBalance(balanceMsat, walletId)
}
}
Unit Conversion Table
| Source | Unit | Internal Storage | Display |
|---|---|---|---|
| LNbits WebSocket | sats | millisats × 1000 | sats ÷ 1000 |
| LNbits API | millisats | millisats | sats ÷ 1000 |
| Payment amounts | millisats | millisats | sats ÷ 1000 |
| UI display | sats | millisats ÷ 1000 | sats |
Error Handling
Connection Errors
private handleWebSocketError(event: Event): void {
console.error('WebSocket error:', event)
this.connectionStatus.value = 'error'
// Log additional error details
if (this.ws) {
console.error('WebSocket state:', this.ws.readyState)
console.error('WebSocket URL:', this.ws.url)
}
}
Connection Close Handling
private handleClose(event: CloseEvent): void {
console.log('WebSocket closed:', event.code, event.reason)
this.isConnected.value = false
this.connectionStatus.value = 'disconnected'
this.ws = null
// Only reconnect if not a normal closure
if (event.code !== 1000) {
this.scheduleReconnect()
}
}
Error Recovery Strategies
Authentication Errors (Code 1002)
// Invalid wallet credentials - clear and require re-authentication
if (event.code === 1002) {
this.authService?.logout()
this.toast.error('Wallet credentials invalid. Please login again.')
}
Network Errors
// Network connectivity issues - retry with backoff
if (!navigator.onLine) {
window.addEventListener('online', () => {
this.reconnectAttempts = 0
this.connectIfNeeded()
}, { once: true })
}
Server Errors (Code 1011)
// Server internal error - longer backoff
if (event.code === 1011) {
this.config.reconnectDelay = 5000 // Increase delay
this.scheduleReconnect()
}
Battery Optimization
VisibilityService Integration
Mobile browsers suspend WebSocket connections when the app loses visibility. The WebSocket service integrates with VisibilityService to handle this efficiently:
protected async onInitialize(): Promise<void> {
// Register with VisibilityService
if (this.visibilityService) {
this.visibilityService.registerService(
this.metadata.name,
this.onResume.bind(this),
this.onPause.bind(this)
)
}
}
Pause Behavior
private async onPause(): Promise<void> {
console.log('WebSocket pausing - app not visible')
// Disconnect to save battery
this.disconnect()
}
Resume Behavior
private async onResume(): Promise<void> {
console.log('WebSocket resuming - app visible')
// Reconnect if not already connected
if (!this.isConnected.value) {
this.reconnectAttempts = 0 // Reset attempt counter
await this.connectIfNeeded()
}
}
Visibility States
| App State | WebSocket Action | Battery Impact |
|---|---|---|
| Visible | Connected | Normal |
| Hidden/Background | Disconnected | Minimal |
| Tab Switch | Disconnected | Minimal |
| Resume | Reconnect | Brief spike |
Configuration
WebSocket Configuration
Configure WebSocket behavior in module config:
// app.config.ts
modules: {
wallet: {
enabled: true,
config: {
websocket: {
enabled: true, // Enable WebSocket functionality
reconnectDelay: 1000, // Initial reconnection delay (ms)
maxReconnectAttempts: 5 // Maximum reconnection attempts
}
}
}
}
Environment Variables
# LNbits server URL - must be accessible for WebSocket
VITE_LNBITS_BASE_URL=https://your-lnbits-server.com
# Development mode - enables verbose WebSocket logging
VITE_DEV_MODE=true
Runtime Configuration Access
// Access configuration in service
const appConfig = (window as any).appConfig
if (appConfig?.modules?.wallet?.config?.websocket) {
this.config = { ...this.config, ...appConfig.modules.wallet.config.websocket }
}
Troubleshooting
Common Issues
WebSocket Connection Fails
Symptoms: Connection status shows 'error' or 'failed'
Debugging Steps:
- Check LNbits server accessibility
- Verify VITE_LNBITS_BASE_URL environment variable
- Check browser network tab for WebSocket connection attempts
- Verify wallet credentials (inkey) are valid
Solutions:
# Test LNbits server connectivity
curl https://your-lnbits-server.com/api/v1/wallet
# Check WebSocket endpoint manually
# Open browser console and test:
const ws = new WebSocket('wss://your-server/api/v1/ws/your-inkey')
Balance Not Updating
Symptoms: Payments are processed but UI balance doesn't change
Debugging Steps:
- Check browser console for WebSocket messages
- Verify PaymentService is receiving updateWalletBalance calls
- Check if wallet ID matches between services
Debug Logging:
// Enable debug logging in WalletWebSocketService
console.log('WebSocket message:', data)
console.log('Balance update:', { old: oldBalance, new: finalBalance })
Frequent Disconnections
Symptoms: WebSocket constantly reconnecting
Potential Causes:
- Network instability
- LNbits server restarts
- Proxy/firewall interference
- Mobile browser background throttling
Solutions:
- Increase reconnectDelay in configuration
- Check server logs for connection issues
- Verify VisibilityService is working correctly
Diagnostic Tools
Connection Status Component
<template>
<div class="websocket-status">
<div :class="statusClass">
{{ connectionStatus }}
</div>
<div v-if="!isConnected">
<button @click="reconnect">Reconnect</button>
</div>
</div>
</template>
<script setup>
const websocketService = injectService(SERVICE_TOKENS.WALLET_WEBSOCKET_SERVICE)
const { isConnected, connectionStatus } = websocketService
const statusClass = computed(() => ({
'status-connected': connectionStatus.value === 'connected',
'status-connecting': connectionStatus.value === 'connecting',
'status-error': connectionStatus.value === 'error'
}))
const reconnect = () => websocketService.reconnect()
</script>
Debug Console Commands
// Access services from browser console
const wsService = window.__DI_CONTAINER__.get('WALLET_WEBSOCKET_SERVICE')
// Check connection status
console.log('Connected:', wsService.isConnected.value)
console.log('Status:', wsService.connectionStatus.value)
// Manual reconnection
wsService.reconnect()
// View configuration
console.log('Config:', wsService.config)
Performance Monitoring
Connection Metrics
// Track connection performance
class ConnectionMetrics {
private connectionAttempts = 0
private successfulConnections = 0
private totalDowntime = 0
private lastConnectTime = 0
onConnectionAttempt() {
this.connectionAttempts++
this.lastConnectTime = Date.now()
}
onConnectionSuccess() {
this.successfulConnections++
const connectTime = Date.now() - this.lastConnectTime
console.log(`Connected in ${connectTime}ms`)
}
getSuccessRate(): number {
return this.successfulConnections / this.connectionAttempts
}
}
See Also
Related Documentation
- index - Main wallet module documentation
- ../../03-core-services/visibility-service - Battery optimization service
- ../../03-core-services/payment-service - Core payment processing
API References
- ../../05-api-reference/lnbits-api - Backend API documentation
- ../../05-api-reference/websocket-protocol - WebSocket message formats
Development Guides
- ../../04-development/testing - Testing WebSocket functionality
- ../../04-development/debugging - Debugging WebSocket issues
Tags: #websocket #real-time #lightning #lnbits #battery-optimization #connection-management Last Updated: 2025-09-18 Author: Development Team