using RelayClient.Crypto; using RelayClient.Models; using WebSocketSharp; using System.Text.Json; namespace RelayClient; public partial class MainPage : ContentPage { private readonly string _username; private readonly WebSocket wsc; private string? _serverPublicKey; public MainPage(string username) { InitializeComponent(); _username = username; UserLabel.Text = $"Logged in as: {_username}"; if (!KeyStorage.HasKeys(_username)) { var keys = E2EeHelper.GenerateRsaKeyPair(); KeyStorage.SavePrivateKey(_username, keys.privateKey); KeyStorage.SavePublicKey(_username, keys.publicKey); } wsc = new WebSocket("ws://localhost:1337/"); wsc.OnMessage += WscOnMessage; wsc.Connect(); var publicKey = KeyStorage.LoadPublicKey(_username); wsc.Send($"REGISTER_KEY|{_username}|{publicKey}"); wsc.Send("GET_SERVER_KEY"); wsc.Send($"GET_HISTORY|{_username}"); } private void SendButton_OnClicked(object? sender, EventArgs e) { SendMessage(); } private void MessageEntry_OnCompleted(object? sender, EventArgs e) { SendMessage(); } private void SendMessage() { var text = MessageEntry.Text?.Trim(); if (string.IsNullOrWhiteSpace(text)) return; if (string.IsNullOrWhiteSpace(_serverPublicKey)) { Console.WriteLine("Server public key not loaded yet."); return; } var encrypted = E2EeHelper.EncryptForRecipient(text, _serverPublicKey); var payload = new SocketEncryptedMessage { Type = "client_encrypted_chat", SenderUsername = _username, CipherText = encrypted.CipherText, Nonce = encrypted.Nonce, Tag = encrypted.Tag, EncryptedKey = encrypted.EncryptedKey }; var json = JsonSerializer.Serialize(payload); wsc.Send(json); Console.WriteLine($"[{_username}] sent encrypted message."); MessageEntry.Text = string.Empty; MessageEntry.Focus(); } private void WscOnMessage(object? sender, MessageEventArgs e) { if (e.Data.StartsWith("SERVER:REGISTERED_KEY:")) { Console.WriteLine(e.Data); return; } Console.WriteLine($"[{_username}] RAW WS DATA: {e.Data}"); try { using var doc = JsonDocument.Parse(e.Data); var root = doc.RootElement; if (!root.TryGetProperty("Type", out var typeElement)) return; var type = typeElement.GetString(); if (type == "server_public_key") { var serverKeyMessage = JsonSerializer.Deserialize(e.Data); if (serverKeyMessage is not null) { _serverPublicKey = serverKeyMessage.PublicKey; Console.WriteLine($"[{_username}] loaded server public key."); } return; } if (type != "encrypted_chat") return; var payload = JsonSerializer.Deserialize(e.Data); if (payload is null) return; if (payload.RecipientUsername != _username) return; Console.WriteLine($"[{_username}] received encrypted payload for {payload.RecipientUsername}"); var privateKey = KeyStorage.LoadPrivateKey(_username); var decryptedText = E2EeHelper.DecryptForRecipient( new EncryptedPayload { CipherText = payload.CipherText, Nonce = payload.Nonce, Tag = payload.Tag, EncryptedKey = payload.EncryptedKey }, privateKey ); Console.WriteLine($"[{_username}] decrypted message from {payload.SenderUsername}: {decryptedText}"); var message = new ChatMessage { SenderUsername = payload.SenderUsername, Text = decryptedText, Timestamp = DateTime.Now }; MainThread.BeginInvokeOnMainThread(async () => { bool isOwnMessage = message.SenderUsername == _username; var bubble = new Border { StrokeThickness = 1, Padding = 10, Margin = isOwnMessage ? new Thickness(40, 0, 0, 0) : new Thickness(0, 0, 40, 0), HorizontalOptions = isOwnMessage ? LayoutOptions.End : LayoutOptions.Start, Content = new VerticalStackLayout { Spacing = 2, Children = { new Label { Text = message.SenderUsername, FontAttributes = FontAttributes.Bold, FontSize = 12 }, new Label { Text = message.Text, FontSize = 14 }, new Label { Text = message.Timestamp.ToString("h:mm tt"), FontSize = 10 } } } }; MessagesLayout.Children.Add(bubble); await MessagesScrollView.ScrollToAsync(MessagesLayout, ScrollToPosition.End, true); }); } catch (Exception ex) { Console.WriteLine($"[{_username}] failed to process websocket message: {ex.Message}"); } } protected override void OnDisappearing() { wsc.OnMessage -= WscOnMessage; wsc.Close(); base.OnDisappearing(); } }