// 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'; import 'package:waylume_server/core/utils.dart'; void initHeartbeat() { // Run this on a separate thread. Isolate.spawn((_) async { // Initialize rolling codes service in this isolate await RollingCodesService.initialize(); // To avoid server deadlock and wait for main registration await Future.delayed(Duration(seconds: 10 + Random().nextInt(5))); // The heartbeat isolate needs to have the operational seed // Since isolates don't share memory, we need to register again in this isolate // This will get the same operational seed since the server_id is the same try { await _registerInHeartbeatIsolate(); } catch (e) { print("Failed to register in heartbeat isolate: $e"); return; } while (true) { DateTime now = DateTime.now().toUtc(); try { // Wait until rolling codes service 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 = 'https://lsdrctuvnwdrzrdyoqzu.supabase.co/functions/v1/server-manager'; 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', }, 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 _registerInHeartbeatIsolate() async { // Generate registration rolling code String registrationAuth = RollingCodesService.generateRegistrationCode(); // Call server-manager registration endpoint String serverManagerUrl = 'https://lsdrctuvnwdrzrdyoqzu.supabase.co/functions/v1/server-manager'; Map requestBody = { 'server_id': fromEnivronment('SERVER_ID')!, 'registration_auth': registrationAuth, }; final response = await http.post( Uri.parse(serverManagerUrl), headers: { 'Content-Type': 'application/json', }, body: jsonEncode(requestBody), ); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); if (responseData['success']) { // Store operational seed in memory for this isolate RollingCodesService.setOperationalSeed(responseData['operational_seed']); print('Heartbeat isolate registered successfully'); } else { throw Exception('Heartbeat isolate registration failed: ${responseData['error']}'); } } else { throw Exception('Heartbeat isolate registration request failed: ${response.statusCode}'); } } 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, }; }