web-app/docs/04-development/mobile-relay-evaluation.md
padreug cdf099e45f Create comprehensive Obsidian-style documentation structure
- Reorganize all markdown documentation into structured docs/ folder
- Create 7 main documentation categories (00-overview through 06-deployment)
- Add comprehensive index files for each category with cross-linking
- Implement Obsidian-compatible [[link]] syntax throughout
- Move legacy/deprecated documentation to archive folder
- Establish documentation standards and maintenance guidelines
- Provide complete coverage of modular architecture, services, and deployment
- Enable better navigation and discoverability for developers and contributors

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-06 14:31:27 +02:00

13 KiB

Mobile Relay Connection Management Evaluation

Current Implementation Analysis

Strengths - What We're Doing Well

1. Basic Mobile Visibility Handling

  • Page Visibility API: Uses document.visibilitychange to detect when app goes to background/foreground
  • Network Status Monitoring: Listens for online/offline events
  • Health Check Integration: Performs health checks when page becomes visible

2. Automatic Reconnection Logic

  • Exponential Backoff: Implements reconnection attempts with delays
  • Max Attempts: Prevents infinite reconnection loops (max 5 attempts)
  • Health Monitoring: 30-second intervals for connection health checks

3. Connection Pool Management

  • SimplePool Integration: Uses nostr-tools SimplePool for efficient relay management
  • Priority-based Connection: Connects to relays in priority order
  • Graceful Degradation: Continues working with partial relay connections

⚠️ Areas for Improvement - Mobile-Specific Issues

1. WebSocket Lifecycle Management

// Current implementation in relayHub.ts:424-456
private setupMobileVisibilityHandling(): void {
  if (typeof document !== 'undefined') {
    this.mobileVisibilityHandler = () => {
      if (document.hidden) {
        console.log('Page hidden, maintaining WebSocket connections')
        // ❌ PROBLEM: Just logs, doesn't optimize connections
      } else {
        console.log('Page visible, resuming normal WebSocket activity')
        this.performHealthCheck() // ✅ Good: Checks health on resume
      }
    }
  }
}

Issues:

  • No Connection Optimization: When app goes to background, WebSockets remain fully active
  • Battery Drain: Maintains all subscriptions and connections in background
  • No Smart Throttling: Doesn't reduce activity when not visible

2. Mobile App State Handling

  • Missing PWA Support: No handling for beforeinstallprompt, appinstalled events
  • No Service Worker Integration: Missing offline/background sync capabilities
  • Limited Mobile-Specific Events: Doesn't handle pagehide/pageshow for better mobile detection

3. Connection Resilience

  • Health Check Limitations: Current health check only verifies relay presence, not actual WebSocket health
  • No Ping/Pong: Missing WebSocket-level health verification
  • Subscription Recovery: Subscriptions aren't automatically restored after reconnection

1. Enhanced Mobile Visibility Handling

private setupMobileVisibilityHandling(): void {
  if (typeof document !== 'undefined') {
    this.mobileVisibilityHandler = () => {
      if (document.hidden) {
        console.log('Page hidden, optimizing WebSocket connections...')
        this.optimizeForBackground()
      } else {
        console.log('Page visible, restoring full WebSocket activity...')
        this.restoreFullActivity()
      }
    }

    // Add mobile-specific events
    document.addEventListener('visibilitychange', this.mobileVisibilityHandler)
    window.addEventListener('pagehide', this.handlePageHide)
    window.addEventListener('pageshow', this.handlePageShow)
  }

  // Handle network changes
  if (typeof window !== 'undefined') {
    window.addEventListener('online', this.handleNetworkOnline)
    window.addEventListener('offline', this.handleNetworkOffline)
    
    // Add connection quality monitoring
    if ('connection' in navigator) {
      navigator.connection?.addEventListener('change', this.handleConnectionChange)
    }
  }
}

private optimizeForBackground(): void {
  // Reduce WebSocket activity in background
  this.backgroundMode = true
  
  // Keep only essential connections
  this.maintainEssentialConnections()
  
  // Reduce health check frequency
  this.adjustHealthCheckInterval(60000) // 1 minute instead of 30 seconds
  
  // Suspend non-critical subscriptions
  this.suspendNonCriticalSubscriptions()
}

private restoreFullActivity(): void {
  this.backgroundMode = false
  
  // Restore all connections
  this.restoreAllConnections()
  
  // Resume normal health check frequency
  this.adjustHealthCheckInterval(30000)
  
  // Restore all subscriptions
  this.restoreAllSubscriptions()
  
  // Perform immediate health check
  this.performHealthCheck()
}

2. Smart Connection Management

interface ConnectionStrategy {
  mode: 'foreground' | 'background' | 'critical'
  maxConnections: number
  healthCheckInterval: number
  subscriptionLimit: number
}

private connectionStrategies: Map<string, ConnectionStrategy> = new Map([
  ['foreground', { mode: 'foreground', maxConnections: 5, healthCheckInterval: 30000, subscriptionLimit: 20 }],
  ['background', { mode: 'background', maxConnections: 2, healthCheckInterval: 60000, subscriptionLimit: 5 }],
  ['critical', { mode: 'critical', maxConnections: 1, healthCheckInterval: 120000, subscriptionLimit: 2 }]
])

private maintainEssentialConnections(): void {
  const strategy = this.connectionStrategies.get('background')!
  
  // Keep only the most reliable relays
  const essentialRelays = this.getMostReliableRelays(strategy.maxConnections)
  
  // Disconnect from non-essential relays
  this.disconnectNonEssentialRelays(essentialRelays)
  
  // Maintain only critical subscriptions
  this.maintainCriticalSubscriptions(strategy.subscriptionLimit)
}

3. Enhanced Health Checking

private async performHealthCheck(): Promise<void> {
  if (!this._isConnected) return

  console.log('Performing enhanced relay health check...')
  const disconnectedRelays: string[] = []
  const unhealthyRelays: string[] = []

  for (const [url, relay] of this.connectedRelays) {
    try {
      // Test actual WebSocket health with ping/pong
      const isHealthy = await this.testRelayHealth(relay)
      
      if (!isHealthy) {
        unhealthyRelays.push(url)
      }
      
      // Update relay status
      this.updateRelayStatus(url, { 
        lastSeen: Date.now(),
        latency: await this.measureRelayLatency(relay)
      })
      
    } catch (error) {
      console.warn(`Health check failed for relay ${url}:`, error)
      disconnectedRelays.push(url)
    }
  }

  // Handle unhealthy relays
  unhealthyRelays.forEach(url => {
    console.log(`Relay ${url} is unhealthy, attempting reconnection...`)
    this.reconnectRelay(url)
  })

  // Handle disconnected relays
  this.handleDisconnectedRelays(disconnectedRelays)
}

private async testRelayHealth(relay: Relay): Promise<boolean> {
  try {
    // Send a ping event to test WebSocket health
    const pingEvent = { kind: 1, content: 'ping', created_at: Math.floor(Date.now() / 1000) }
    await relay.send(pingEvent)
    
    // Wait for response or timeout
    return new Promise((resolve) => {
      const timeout = setTimeout(() => resolve(false), 5000)
      
      const onMessage = (event: any) => {
        if (event.kind === 1 && event.content === 'pong') {
          clearTimeout(timeout)
          relay.off('message', onMessage)
          resolve(true)
        }
      }
      
      relay.on('message', onMessage)
    })
  } catch (error) {
    return false
  }
}

4. Subscription Recovery System

interface SubscriptionState {
  id: string
  filters: Filter[]
  relays?: string[]
  onEvent?: (event: Event) => void
  onEose?: () => void
  onClose?: () => void
  isActive: boolean
  lastActivity: number
}

private subscriptionStates: Map<string, SubscriptionState> = new Map()

subscribe(config: SubscriptionConfig): () => void {
  // Store subscription state for recovery
  this.subscriptionStates.set(config.id, {
    ...config,
    isActive: true,
    lastActivity: Date.now()
  })

  // Perform actual subscription
  const unsubscribe = this.performSubscription(config)
  
  return () => {
    this.subscriptionStates.get(config.id)!.isActive = false
    unsubscribe()
  }
}

private restoreAllSubscriptions(): void {
  console.log('Restoring all subscriptions...')
  
  for (const [id, state] of this.subscriptionStates) {
    if (state.isActive) {
      console.log(`Restoring subscription: ${id}`)
      this.performSubscription(state)
    }
  }
}

5. Mobile-Specific Event Handling

private handlePageHide = (event: PageTransitionEvent): void => {
  // Handle mobile app backgrounding
  if (event.persisted) {
    console.log('Page is being cached (mobile background)')
    this.optimizeForBackground()
  } else {
    console.log('Page is being unloaded')
    this.prepareForUnload()
  }
}

private handlePageShow = (event: PageTransitionEvent): void => {
  console.log('Page is being shown (mobile foreground)')
  
  // Check if we need to reconnect
  if (!this._isConnected) {
    console.log('Reconnecting after page show...')
    this.connect()
  } else {
    this.restoreFullActivity()
  }
}

private handleConnectionChange = (event: Event): void => {
  const connection = (event.target as any) as NetworkInformation
  
  console.log(`Connection type changed: ${connection.effectiveType}`)
  
  // Adjust connection strategy based on network quality
  if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
    this.setConnectionStrategy('critical')
  } else if (connection.effectiveType === '3g') {
    this.setConnectionStrategy('background')
  } else {
    this.setConnectionStrategy('foreground')
  }
}

📱 Mobile-Specific Best Practices Implementation

1. Battery-Aware Connection Management

private isLowBattery = false

private setupBatteryMonitoring(): void {
  if ('getBattery' in navigator) {
    navigator.getBattery().then(battery => {
      battery.addEventListener('levelchange', () => {
        this.isLowBattery = battery.level < 0.2
        this.adjustConnectionStrategy()
      })
      
      battery.addEventListener('chargingchange', () => {
        this.adjustConnectionStrategy()
      })
    })
  }
}

private adjustConnectionStrategy(): void {
  if (this.isLowBattery && !this.isCharging) {
    this.setConnectionStrategy('critical')
  } else {
    this.setConnectionStrategy(this.backgroundMode ? 'background' : 'foreground')
  }
}

2. Progressive Web App Support

private setupPWASupport(): void {
  // Handle app installation
  window.addEventListener('beforeinstallprompt', (event) => {
    console.log('App can be installed')
    this.installPrompt = event
  })

  // Handle app installation completion
  window.addEventListener('appinstalled', () => {
    console.log('App was installed')
    this.isInstalled = true
  })

  // Handle app launch from installed state
  if (window.matchMedia('(display-mode: standalone)').matches) {
    console.log('App is running in standalone mode')
    this.isStandalone = true
  }
}

3. Service Worker Integration

// In service worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'relay-reconnect') {
    event.waitUntil(reconnectToRelays())
  }
})

// In relay hub
private scheduleBackgroundReconnect(): void {
  if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
    navigator.serviceWorker.ready.then(registration => {
      registration.sync.register('relay-reconnect')
    })
  }
}

🧪 Testing Recommendations

1. Mobile Device Testing

  • Test on actual iOS/Android devices
  • Test background/foreground transitions
  • Test network switching (WiFi ↔ Cellular)
  • Test low battery scenarios

2. Simulation Testing

// Test visibility changes
document.dispatchEvent(new Event('visibilitychange'))

// Test network changes
window.dispatchEvent(new Event('offline'))
window.dispatchEvent(new Event('online'))

// Test page transitions
window.dispatchEvent(new PageTransitionEvent('pagehide', { persisted: true }))
window.dispatchEvent(new PageTransitionEvent('pageshow', { persisted: false }))

3. Performance Monitoring

// Monitor connection efficiency
const connectionMetrics = {
  connectionAttempts: 0,
  successfulConnections: 0,
  failedConnections: 0,
  averageLatency: 0,
  batteryImpact: 'low' // 'low' | 'medium' | 'high'
}

📊 Current Implementation Score

Category Score Notes
Basic Mobile Support 6/10 Has visibility API but limited optimization
Connection Resilience 7/10 Good reconnection logic, needs better health checks
Battery Optimization 3/10 No battery-aware connection management
Network Adaptation 5/10 Basic online/offline handling
Subscription Recovery 4/10 Subscriptions lost on reconnection
Mobile-Specific Events 4/10 Missing key mobile lifecycle events

Overall Score: 4.8/10

🎯 Priority Implementation Order

  1. High Priority: Enhanced health checking and subscription recovery
  2. Medium Priority: Smart background/foreground connection management
  3. Low Priority: Battery monitoring and PWA support

🚀 Next Steps

  1. Implement enhanced health checking with ping/pong
  2. Add subscription state persistence and recovery
  3. Implement smart connection optimization for background mode
  4. Add mobile-specific event handling
  5. Test thoroughly on mobile devices
  6. Monitor battery impact and connection efficiency

The current implementation provides a solid foundation but needs significant enhancement to meet mobile best practices for WebSocket management, battery optimization, and connection resilience.