// Run a heartbeat to let supabase know that the client is still active. import 'dart:io'; import 'dart:isolate'; import 'dart:math'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:waylume_server/config/supabase_config.dart'; import 'package:waylume_server/services/rolling_codes_service.dart'; void initHeartbeat() { // Run this on a separate thread. Isolate.spawn((_) async { // To avoid server deadlock await Future.delayed(Duration(seconds: Random().nextInt(5))); while (true) { DateTime now = DateTime.now().toUtc(); try { // Wait until rolling codes service is initialized and has operational seed if (!RollingCodesService.isRegistered) { print("Server not registered yet, skipping heartbeat"); await Future.delayed(Duration(seconds: 10)); // Check more frequently during startup continue; } // Generate operational rolling code for heartbeat authentication String authCode = RollingCodesService.generateOperationalCode(); // Get server status (CPU, memory, active connections) Map serverStatus = await _getServerStatus(); // Send heartbeat to server-manager endpoint String serverManagerUrl = '${fromEnivronment("SUPABASE_URL")}/functions/v1/server-manager/heartbeat'; Map heartbeatData = { 'server_id': fromEnivronment("SERVER_ID")!, 'timestamp': now.toIso8601String(), 'auth_code': authCode, 'status': serverStatus }; final response = await http.post( Uri.parse(serverManagerUrl), headers: { 'Content-Type': 'application/json', 'apikey': fromEnivronment('SUPABASE_ANON_KEY')!, }, body: jsonEncode(heartbeatData), ); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); if (responseData['success']) { print("Heartbeat sent successfully at ${now.toIso8601String()}"); } else { print("Heartbeat failed: ${responseData['error']}"); } } else { print("Heartbeat request failed: ${response.statusCode}"); } } catch (e) { print("Error sending heartbeat: $e"); } // Wait 120 seconds (2 minutes) before sending the next heartbeat await Future.delayed(Duration(seconds: 120)); } }, null); } Future> _getServerStatus() async { // Get basic server status information // This is a simplified implementation - could be enhanced with actual metrics int activeConnections = 0; double cpuUsage = 0.0; double memoryUsage = 0.0; try { // Get active WireGuard connections count ProcessResult wgShowResult = await Process.run('wg', ['show', 'wg0']); if (wgShowResult.exitCode == 0) { // Count peer entries (simplified - each peer has multiple lines) String output = wgShowResult.stdout as String; activeConnections = 'peer:'.allMatches(output).length; } } catch (e) { print("Error getting WireGuard status: $e"); } try { // Get basic system info (simplified) ProcessResult uptimeResult = await Process.run('uptime', []); if (uptimeResult.exitCode == 0) { String output = uptimeResult.stdout as String; // Parse load average as a rough CPU usage indicator RegExp loadRegex = RegExp(r'load average: (\d+\.?\d*),'); Match? match = loadRegex.firstMatch(output); if (match != null) { cpuUsage = double.tryParse(match.group(1) ?? '0') ?? 0.0; cpuUsage = (cpuUsage * 100).clamp(0, 100); // Convert to percentage } } } catch (e) { print("Error getting CPU usage: $e"); } try { // Get memory usage (simplified) ProcessResult freeResult = await Process.run('free', ['-m']); if (freeResult.exitCode == 0) { String output = freeResult.stdout as String; List lines = output.split('\n'); if (lines.length > 1) { List memLine = lines[1].split(RegExp(r'\s+')); if (memLine.length > 2) { int total = int.tryParse(memLine[1]) ?? 1; int used = int.tryParse(memLine[2]) ?? 0; memoryUsage = (used / total * 100).clamp(0, 100); } } } } catch (e) { print("Error getting memory usage: $e"); } return { 'active_connections': activeConnections, 'cpu_usage': cpuUsage, 'memory_usage': memoryUsage, }; }