Files
waylume_server/lib/services/supabase_heartbeat.dart

104 lines
3.3 KiB
Dart

// 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(String operationalSeed) {
// Run this on a separate thread.
Isolate.spawn((String seed) async {
// Initialize rolling codes service in this isolate
await RollingCodesService.initialize();
// Set the operational seed that was passed from main isolate
RollingCodesService.setOperationalSeed(seed);
// To avoid server deadlock and wait for main registration
await Future.delayed(Duration(seconds: 10 + Random().nextInt(5)));
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 basic server status (only active connections)
Map<String, dynamic> serverStatus = await _getServerStatus();
// Send heartbeat to server-manager endpoint
String serverManagerUrl = 'https://lsdrctuvnwdrzrdyoqzu.supabase.co/functions/v1/server-manager';
Map<String, dynamic> 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 90 seconds (1.5 minutes) before sending the next heartbeat
await Future.delayed(Duration(seconds: 90));
}
}, operationalSeed);
}
Future<Map<String, dynamic>> _getServerStatus() async {
int activeConnections = 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");
}
return {
'active_connections': activeConnections,
};
}