diff --git a/RelayClient/MainPage.xaml.cs b/RelayClient/MainPage.xaml.cs index 5dd799e..f9c3a32 100644 --- a/RelayClient/MainPage.xaml.cs +++ b/RelayClient/MainPage.xaml.cs @@ -17,7 +17,7 @@ public partial class MainPage : ContentPage private readonly Dictionary> _messagesByChannel = new(); private readonly List _channels = []; - + public MainPage(string username) { InitializeComponent(); @@ -33,7 +33,7 @@ public partial class MainPage : ContentPage } _wsc = new WebSocket("ws://localhost:1337/"); - + _wsc.OnMessage += WscOnMessage; _wsc.Connect(); @@ -41,7 +41,7 @@ public partial class MainPage : ContentPage _wsc.Send($"REGISTER_KEY|{_username}|{publicKey}"); _wsc.Send("GET_SERVER_KEY"); _wsc.Send("GET_CHANNELS"); - + hybridWebView.SetInvokeJavaScriptTarget(this); ServerAPI.setupClient(); @@ -69,7 +69,7 @@ public partial class MainPage : ContentPage Console.WriteLine("Server public key not loaded yet."); return; } - + if (string.IsNullOrWhiteSpace(_currentChannelId)) { Console.WriteLine("No channel selected yet."); @@ -105,9 +105,9 @@ public partial class MainPage : ContentPage Console.WriteLine(e.Data); return; } - + // SafeSendRawToWebView($"[{_username}] RAW WS DATA: {e.Data}"); - + Console.WriteLine($"[{_username}] RAW WS DATA: {e.Data}"); try @@ -118,7 +118,7 @@ public partial class MainPage : ContentPage if (!root.TryGetProperty("Type", out var typeElement)) return; - var type = (SignalType) typeElement.GetInt32(); + var type = (SignalType)typeElement.GetInt32(); if (type == SignalType.ChannelList) { @@ -164,7 +164,7 @@ public partial class MainPage : ContentPage return; } - + if (type == SignalType.EncryptedSignal) { var payload = JsonSerializer.Deserialize(e.Data); @@ -189,7 +189,7 @@ public partial class MainPage : ContentPage }, privateKey ); - + var rtcSignal = JsonSerializer.Deserialize(decryptedJson); if (rtcSignal is null) @@ -211,76 +211,7 @@ public partial class MainPage : ContentPage return; } - - if (type is SignalType.OfferUpdated or SignalType.AnswerUpdated or SignalType.CandidateAdded or SignalType.CallLeft) - { - var rtcNotification = JsonSerializer.Deserialize(e.Data); - if (rtcNotification is null) - return; - var notificationType = rtcNotification.Type; - var notificationChannelId = rtcNotification.ChannelId ?? string.Empty; - - if (notificationChannelId != _currentChannelId) - return; - - // SafeSendRawToWebView("RTC notification received: " + notificationType + " for " + notificationChannelId); - - MainThread.BeginInvokeOnMainThread(async () => - { - switch (notificationType) - { - case SignalType.OfferUpdated: - { - if (rtcNotification.Username == _username) - break; - - var offer = await GetRtcOffer(); - await SendRtcSignalToJsAsync(offer); - break; - } - case SignalType.AnswerUpdated: - { - var answer = await ServerAPI.GetAnswerForChannelAsync(_currentChannelId); - if (answer is not null) - { - await AnswerCallback(answer); - } - break; - } - case SignalType.CandidateAdded: - { - if (rtcNotification.Username == _username) - break; - - try - { - IceCandidate? iceCandidate = JsonSerializer.Deserialize(rtcNotification.Direction); - - if (iceCandidate is null) - break; - - IceCandidateCallback(iceCandidate); - } - catch (Exception ex) - { - SafeSendRawToWebView($"Candidate rejected: {ex.Message}"); - } - - break; - } - case SignalType.CallLeft: - { - SafeSendRawToWebView("RTC call left notification received."); - RtcLeaveCallback(); - break; - } - } - }); - - return; - } - if (type != SignalType.EncryptedChat) return; @@ -360,14 +291,14 @@ public partial class MainPage : ContentPage { _currentChannelId = channel.ChannelId; _currentChannelName = channel.Name; - + MainThread.BeginInvokeOnMainThread(async () => { await PushRtcContextToJsAsync(); if (channel.Type == ChannelType.Voice) { SwapView(); - // JoinRtcChannel(); //TODO: Join voice calls when clicking channel rather than a separate button + _ = JoinRtcChannel(); //TODO: Join voice calls when clicking channel rather than a separate button } }); @@ -446,28 +377,23 @@ public partial class MainPage : ContentPage ViewSwapped.Text = "Swap to Message View"; } } + private void SwapView_OnClicked(object? sender, EventArgs e) { SwapView(); } #region RTC Functions - public async Task JoinRtcChannel() + + public Task JoinRtcChannel() { if (string.IsNullOrWhiteSpace(_currentChannelId)) - return; //false; + return Task.CompletedTask; _wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}"); - - // SafeSendRawToWebView($"Attempting to join RTC Channel {_currentChannelName} | {_currentChannelId} "); - - //bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId); - - //SafeSendRawToWebView($"Rtc Channel {_currentChannelName} | {_currentChannelId} is active: {active}"); - - return; //active; + return Task.CompletedTask; } - + public void LeaveRtcChannel() { if (string.IsNullOrWhiteSpace(_currentChannelId)) @@ -475,151 +401,9 @@ public partial class MainPage : ContentPage _wsc.Send($"RTC_LEAVE_CHANNEL|{_username}|{_currentChannelId}"); } - - public async void WriteRtcOffer(string json) - { - try - { - RtcDescription? description = JsonSerializer.Deserialize(json); - DBOffer offer = new DBOffer - { - ChannelId = _currentChannelId, - Username = _username, - SessionDescription = description - }; - var response = await ServerAPI.PostOfferAsync(offer); - SafeSendRawToWebView(response.ToString()); - } - catch (Exception ex) - { - SafeSendRawToWebView($"WriteRtcOffer failed: {ex.Message}"); - } - } - public async Task GetRtcOffer() - { - RtcDescription? offer = await ServerAPI.GetOffersForChannelAsync(_currentChannelId); - return JsonSerializer.Serialize(offer); - } - - public async void WriteRtcAnswer(string json) - { - // SafeSendRawToWebView("WriteRtcAnswer entered with: " + json); - - try - { - RtcDescription? description = JsonSerializer.Deserialize(json); - DBOffer answer = new DBOffer - { - ChannelId = _currentChannelId, - Username = _username, - SessionDescription = description - }; - await ServerAPI.PostAnswerAsync(answer); - SafeSendRawToWebView("WriteRtcAnswer posted successfully"); - } - catch (Exception ex) - { - SafeSendRawToWebView("WriteRtcAnswer failed: " + ex.Message); - } - } - - public async void WriteIceCandidate(string json) - { - try - { - IceCandidate? candidate = JsonSerializer.Deserialize(json); - DBIceCandidate DBCandidate = new DBIceCandidate - { - ChannelId = _currentChannelId, - Username = _username, - Candidate = candidate - }; - if (candidate == null) return; - await ServerAPI.PostIceCandidateAsync(DBCandidate); - } - catch (Exception ex) - { - SafeSendRawToWebView("WriteIceCandidate failed: " + ex.Message); - } - } - - public async void IceCandidateCallback(IceCandidate candidate) - { - try - { - await hybridWebView.InvokeJavaScriptAsync("IceCandidateAdded", [candidate], [HybridJSType.Default.IceCandidate]); - } - catch (Exception ex) - { - SafeSendRawToWebView("WriteIceCandidate failed: " + ex.Message); - } - } - public async Task AnswerCallback(RtcDescription answer) - { - answer.sdp = answer.sdp.Replace("\r\n", "(rn)"); - try - { - await hybridWebView.InvokeJavaScriptAsync("AnswerCallbackJS", [answer], [HybridJSType.Default.RtcDescription]); - } - catch (Exception ex) - { - SafeSendRawToWebView("AnswerCallback failed: " + ex.Message); - } - } - - public async void RtcLeaveCallback() - { - try - { - await hybridWebView.InvokeJavaScriptAsync("RtcLeaveCall", [], []); - } - catch (Exception ex) - { - SafeSendRawToWebView("RtcLeaveCallback failed: " + ex.Message); - } - } - - private Task SendRtcSignalToJsAsync(string rawJson) - { - MainThread.BeginInvokeOnMainThread(async () => - { - try - { - SafeSendRawToWebView("Dispatching RTC signal to JS"); - - var jsArg = JsonSerializer.Serialize(rawJson); - - await hybridWebView.EvaluateJavaScriptAsync( - $"window.RelaySocket.receiveRtcSignal({jsArg})" - ); - - SafeSendRawToWebView("RTC signal dispatched to JS"); - } - catch (Exception ex) - { - SafeSendRawToWebView("SendRtcSignalToJsAsync failed: " + ex.Message); - } - }); - - return Task.CompletedTask; - } //Remove? - - private async Task PushRtcContextToJsAsync() - { - var usernameJson = JsonSerializer.Serialize(_username); - var channelIdJson = JsonSerializer.Serialize(_currentChannelId); - - await hybridWebView.EvaluateJavaScriptAsync($"window.setUsername({usernameJson})"); - await hybridWebView.EvaluateJavaScriptAsync($"window.setChannelId({channelIdJson})"); - - Console.WriteLine($"[{_username}] pushed RTC context into HybridWebView."); - } //Remove? - public void SendRtcSignal(string json) { - SafeSendRawToWebView("SendRtcSignal entered: " + json); - if (string.IsNullOrWhiteSpace(_serverPublicKey)) { SafeSendRawToWebView("SendRtcSignal failed: server public key not loaded."); @@ -639,20 +423,25 @@ public partial class MainPage : ContentPage } if (rtcSignal is null) - { - SafeSendRawToWebView("SendRtcSignal failed: rtcSignal was null."); return; - } + + if (string.IsNullOrWhiteSpace(rtcSignal.ChannelId)) + rtcSignal.ChannelId = _currentChannelId; + + if (string.IsNullOrWhiteSpace(rtcSignal.From)) + rtcSignal.From = _username; if (string.IsNullOrWhiteSpace(rtcSignal.ChannelId)) { - SafeSendRawToWebView("SendRtcSignal failed: channelId was empty."); + SafeSendRawToWebView("SendRtcSignal failed: missing channel id."); return; } + var outgoingJson = JsonSerializer.Serialize(rtcSignal); + try { - var encrypted = E2EeHelper.EncryptForRecipient(json, _serverPublicKey); + var encrypted = E2EeHelper.EncryptForRecipient(outgoingJson, _serverPublicKey); var payload = new SocketRtcSignalMessage { @@ -665,30 +454,72 @@ public partial class MainPage : ContentPage EncryptedKey = encrypted.EncryptedKey }; - var socketJson = JsonSerializer.Serialize(payload); - _wsc.Send(socketJson); + _wsc.Send(JsonSerializer.Serialize(payload)); - SafeSendRawToWebView($"SendRtcSignal sent: {rtcSignal.Type} -> {rtcSignal.ChannelId}"); - Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}"); + SafeSendRawToWebView($"SendRtcSignal sent: {rtcSignal.Type} -> {rtcSignal.To}"); } catch (Exception ex) { - SafeSendRawToWebView("SendRtcSignal websocket/encrypt failed: " + ex.Message); + SafeSendRawToWebView("SendRtcSignal failed: " + ex.Message); } - } //Remove? - + } + public async Task GetRtcParticipants() { + if (string.IsNullOrWhiteSpace(_currentChannelId)) + return "[]"; + var participants = await ServerAPI.GetRtcParticipantsAsync(_currentChannelId); - return JsonSerializer.Serialize(participants); + return JsonSerializer.Serialize(participants ?? []); } - - #endregion - private void OnSendMessageButtonClicked(object sender, EventArgs e) + + private Task SendRtcSignalToJsAsync(string rawJson) { - SafeSendRawToWebView($"Hello from C#!"); + MainThread.BeginInvokeOnMainThread(async () => + { + try + { + var jsArg = JsonSerializer.Serialize(rawJson); + + await hybridWebView.EvaluateJavaScriptAsync($@" + try {{ + window.HybridWebView.SendRawMessage('C# eval entered'); + + if (!window.RelaySocket) {{ + window.HybridWebView.SendRawMessage('window.RelaySocket missing'); + }} else if (typeof window.RelaySocket.receiveRtcSignal !== 'function') {{ + window.HybridWebView.SendRawMessage('RelaySocket.receiveRtcSignal missing'); + }} else {{ + window.HybridWebView.SendRawMessage('Calling RelaySocket.receiveRtcSignal'); + window.RelaySocket.receiveRtcSignal({jsArg}); + }} + }} catch (err) {{ + window.HybridWebView.SendRawMessage('RTC JS dispatch failed: ' + err); + }} + "); + } + catch (Exception ex) + { + SafeSendRawToWebView("SendRtcSignalToJsAsync failed: " + ex.Message); + } + }); + + return Task.CompletedTask; } + private async Task PushRtcContextToJsAsync() + { + var usernameJson = JsonSerializer.Serialize(_username); + var channelIdJson = JsonSerializer.Serialize(_currentChannelId); + + await hybridWebView.EvaluateJavaScriptAsync($"window.setUsername({usernameJson})"); + await hybridWebView.EvaluateJavaScriptAsync($"window.setChannelId({channelIdJson})"); + + Console.WriteLine($"[{_username}] pushed RTC context into HybridWebView."); + } + + #endregion + private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e) { if (e.Message == "rtc_page_ready")