using System; using System.Collections.Generic; using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Godot; using HttpClient = System.Net.Http.HttpClient; namespace SessionZeroClient.API; public class ApiHandler : IApiHandler { private readonly HttpClient _httpClient; public ApiHandler() { var handler = new HttpClientHandler { UseDefaultCredentials = true, }; _httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(5), }; } /// /// Returns true if the server URL is reachable valid, false otherwise. /// /// /// public async Task ValidateServerUrl(string url) { try { var apiString = $"{url}/api/Database/validate-server"; using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var response = await _httpClient.GetAsync(apiString, cts.Token); if (!response.IsSuccessStatusCode) { return false; } var responseString = await response.Content.ReadAsStringAsync(cts.Token); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var validationResponse = JsonSerializer.Deserialize(responseString, options); return validationResponse?.IsValid ?? false; } catch { throw new HttpRequestException($"Server {url} is invalid or not reachable."); return false; } } /// /// Register a new user with the server. /// /// /// /// /// /// /// public async Task Register(string url, string username, string email, string password) { bool isValidServer = await ValidateServerUrl(url); if (!isValidServer) { throw new HttpRequestException($"Could not Register User, Server {url} is invalid or not reachable."); } var apiString = $"{url}/api/Auth/register"; var requestBody = new Dictionary { {"username", username}, {"email", email}, {"password", password} }; var jsonBody = JsonSerializer.Serialize(requestBody); var content = new StringContent(jsonBody, System.Text.Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(apiString, content); if (!response.IsSuccessStatusCode) { // throw new HttpRequestException($"Register failed: {response.StatusCode}"); } var responseString = await response.Content.ReadAsStringAsync(); return responseString; } /// /// Login to the server with the given username and password. Returns the JWT token if successful. /// /// /// /// /// /// public async Task Login(string url, string username, string password) { bool isValidServer = await ValidateServerUrl(url); if (!isValidServer) { throw new HttpRequestException($"Could not Login, Server {url} is invalid or not reachable."); } var apiString = $"{url}/api/Auth/login"; var requestBody = new Dictionary { {"username", username}, {"password", password} }; var jsonBody = JsonSerializer.Serialize(requestBody); var content = new StringContent(jsonBody, System.Text.Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(apiString, content); if (!response.IsSuccessStatusCode) { // throw new HttpRequestException($"Login failed: {response.StatusCode}"); } var responseString = await response.Content.ReadAsStringAsync(); return responseString; } public class ServerUrlValidationResponse { public bool IsValid { get; set; } public string Message { get; set; } } } ----------------------------------------------------- using System.Threading.Tasks; namespace SessionZeroClient.API; public interface IApiHandler { Task ValidateServerUrl(string url); Task Register(string url, string username, string email, string password); Task Login(string url, string username, string password); } ------------------------------------------------------ using Godot; using System; using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; using SessionZeroClient.API; namespace SessionZeroClient; public class AccountManager(IApiHandler apiHandler) { public string Username { get; private set; } = ""; public string Email { get; private set; } = ""; public string Jwt { get; private set; } = ""; private IApiHandler _apiHandler = apiHandler; public async Task Register(string url, string username, string email, string password) { var response = await _apiHandler.Register(url, username, email, password); if (!string.IsNullOrEmpty(response)) { var responseData = JsonSerializer.Deserialize>(response); if (responseData != null && responseData.ContainsKey("message") && responseData["message"] == "User registered successfully") { return true; } } return false; } public async Task Login(string url, string username, string password) { var response = await _apiHandler.Login(url, username, password); if (!string.IsNullOrEmpty(response)) { var responseData = JsonSerializer.Deserialize>(response); if (responseData != null && responseData.ContainsKey("token")) { Jwt = responseData["token"]; Username = username; return true; } } return false; } public void Logout() { Jwt = ""; Username = ""; Email = ""; } } ----------------------------------------------------------- using Godot; using System; using System.Threading.Tasks; using SessionZeroClient; using SessionZeroClient.API; public partial class AccountScreenLogic : Node { [ExportGroup("UI Elements")] // Selection Screen [ExportSubgroup("Selection Screen")] [Export] private VBoxContainer SelectionScreenVboxContainer { get; set; } [Export] private Button RegisterSelectionButton { get; set; } [Export] private Button LoginSelectionButton { get; set; } [Export] private LineEdit UrlLineEdit { get; set; } // Register Screen [ExportSubgroup("Register Screen")] [Export] private VBoxContainer RegisterScreenVboxContainer { get; set; } [Export] private LineEdit RegisterUsernameLineEdit { get; set; } [Export] private LineEdit RegisterEmailLineEdit { get; set; } [Export] private LineEdit RegisterPasswordLineEdit { get; set; } [Export] private Button RegisterButton { get; set; } // Login Screen [ExportSubgroup("Login Screen")] [Export] private VBoxContainer LoginScreenVboxContainer { get; set; } [Export] private LineEdit LoginUsernameLineEdit { get; set; } [Export] private LineEdit LoginPasswordLineEdit { get; set; } [Export] private Button LoginButton { get; set; } // Account Screen [ExportSubgroup("Account Screen")] [Export] private VBoxContainer AccountScreenVboxContainer { get; set; } [Export] private Label AccountScreenGreetingLabel { get; set; } [Export] private Button LogoutButton { get; set; } [Export] private Button OfflineModeButton { get; set; } // Other [ExportSubgroup("Other")] [Export] Button BackButton { get; set; } [Export] RichTextLabel StatusLabel { get; set; } private IApiHandler _apiHandler; private AccountManager _accountManager; private string _url = ""; public void Init(IApiHandler apiHandler, AccountManager accountManager) { _apiHandler = apiHandler; _accountManager = accountManager; SetupSignals(); InitialState(); } private void InitialState() { SelectionScreenVboxContainer.Visible = true; RegisterScreenVboxContainer.Visible = false; LoginScreenVboxContainer.Visible = false; AccountScreenVboxContainer.Visible = false; BackButton.Visible = false; } private void RegisterState() { SelectionScreenVboxContainer.Visible = false; RegisterScreenVboxContainer.Visible = true; LoginScreenVboxContainer.Visible = false; AccountScreenVboxContainer.Visible = false; BackButton.Visible = true; } private void LoginState() { SelectionScreenVboxContainer.Visible = false; RegisterScreenVboxContainer.Visible = false; LoginScreenVboxContainer.Visible = true; AccountScreenVboxContainer.Visible = false; BackButton.Visible = true; } private void AccountSuccessState() { SelectionScreenVboxContainer.Visible = false; RegisterScreenVboxContainer.Visible = false; LoginScreenVboxContainer.Visible = false; AccountScreenVboxContainer.Visible = true; BackButton.Visible = false; AccountScreenGreetingLabel.Text = $"Welcome, {_accountManager.Username}!"; } private void SetupSignals() { // Selection Screen RegisterSelectionButton.Pressed += RegisterSelectionButtonPressed; LoginSelectionButton.Pressed += LoginSelectionButtonPressed; OfflineModeButton.Pressed += () => GD.Print("Offline mode not implemented yet."); // Register Screen RegisterButton.Pressed += RegisterButtonPressed; // Login Screen LoginButton.Pressed += LoginButtonPressed; // Account Screen LogoutButton.Pressed += Logout; // Other BackButton.Pressed += BackButtonPressed; } private async void RegisterSelectionButtonPressed() { bool isValidServer = await ValidateServerUrl(UrlLineEdit.Text); if (isValidServer) { _url = UrlLineEdit.Text; RegisterState(); } } private async void LoginSelectionButtonPressed() { bool isValidServer = await ValidateServerUrl(UrlLineEdit.Text); if (isValidServer) { _url = UrlLineEdit.Text; LoginState(); } } private async void RegisterButtonPressed() { var username = RegisterUsernameLineEdit.Text; var email = RegisterEmailLineEdit.Text; var password = RegisterPasswordLineEdit.Text; var success = await _accountManager.Register(_url, username, email, password); GD.Print($"Register success: {success}"); SetStatusLabel($"Register success: {success}", success ? Colors.Green : Colors.Red); if (success) { var loginSuccess = await _accountManager.Login(_url, username, password); GD.Print($"Login success: {loginSuccess}"); SetStatusLabel($"Login success: {loginSuccess}", loginSuccess ? Colors.Green : Colors.Red); if (loginSuccess) { AccountSuccessState(); } else { SetStatusLabel($"Login failed: {loginSuccess}", Colors.Red); } } else { SetStatusLabel("Registration failed", Colors.Red); } } private async void LoginButtonPressed() { var username = LoginUsernameLineEdit.Text; var password = LoginPasswordLineEdit.Text; var success = await _accountManager.Login(_url, username, password); GD.Print($"Login success: {success}"); SetStatusLabel($"Login success: {success}", success ? Colors.Green : Colors.Red); if (success) { AccountSuccessState(); } else { SetStatusLabel("Login failed. Check Username and Password.", Colors.Red); } } private async Task ValidateServerUrl(string url) { if (string.IsNullOrEmpty(url)) { return false; } try { var isValid = await _apiHandler.ValidateServerUrl(url); if (isValid) { GD.Print($"Server {url} is valid."); SetStatusLabel($"Server {url} is valid.", Colors.Green); } return isValid; } catch (Exception ex) { GD.Print($"Server validation error: {ex.Message}"); SetStatusLabel($"Server validation error: {ex.Message}", Colors.Red); return false; } } private void Logout() { _accountManager.Logout(); GD.Print("Logged out"); SetStatusLabel("Logged out", Colors.Cyan); InitialState(); } private void BackButtonPressed() { InitialState(); } private void SetStatusLabel(string message, Color color) { StatusLabel.Text = $"[color={color.ToHtml()}] {message} [/color]"; } }