144 lines
4.7 KiB
Dart
144 lines
4.7 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() {
|
|
|
|
// 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 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));
|
|
}
|
|
|
|
}, null);
|
|
|
|
}
|
|
|
|
Future<void> _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<String, dynamic> 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<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,
|
|
};
|
|
} |