diff --git a/RelayClient/MainPage.xaml.cs b/RelayClient/MainPage.xaml.cs index 406bbf2..e667ff9 100644 --- a/RelayClient/MainPage.xaml.cs +++ b/RelayClient/MainPage.xaml.cs @@ -197,6 +197,55 @@ public partial class MainPage : ContentPage return; } + if (type == "rtc_offer_updated" || type == "rtc_answer_updated" || type == "rtc_candidate_added" || type == "rtc_call_left") + { + var rtcNotification = JsonSerializer.Deserialize(e.Data); + if (rtcNotification is null) + return; + + var notificationType = rtcNotification.Type ?? string.Empty; + var notificationChannelId = rtcNotification.ChannelId ?? string.Empty; + + if (notificationChannelId != _currentChannelId) + return; + + SafeSendRawToWebView("RTC notification received: " + notificationType + " for " + notificationChannelId); + + MainThread.BeginInvokeOnMainThread(async () => + { + switch (notificationType) + { + case "rtc_offer_updated": + { + var offer = await GetRtcOffer(); + await SendRtcSignalToJsAsync(offer); + break; + } + case "rtc_answer_updated": + { + var answer = await ServerAPI.GetAnswerForChannelAsync(_currentChannelId); + if (answer is not null) + { + var json = JsonSerializer.Serialize(answer); + await hybridWebView.EvaluateJavaScriptAsync($"window.AnswerCallback({json})"); + } + break; + } + case "rtc_candidate_added": + { + break; + } + case "rtc_call_left": + { + SafeSendRawToWebView("RTC call left notification received."); + break; + } + } + }); + + return; + } + if (type != "encrypted_chat") return; @@ -358,15 +407,28 @@ public partial class MainPage : ContentPage public async Task JoinRtcChannel() { - //TODO: get bool value for if channel ID has an active call - //TODO: Join RTC using current channel ID - SafeSendRawToWebView($"Attempting to join RTC Channel {_currentChannelName}"); - bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId); - SafeSendRawToWebView($"Rtc Channel {_currentChannelName} is active: {active}"); - return active; - //await hybridWebView.EvaluateJavaScriptAsync($"window.channelCallJoin({active})"); + if (string.IsNullOrWhiteSpace(_currentChannelId)) + return false; + _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; } + + public void LeaveRtcChannel() + { + if (string.IsNullOrWhiteSpace(_currentChannelId)) + return; + + _wsc.Send($"RTC_LEAVE_CHANNEL|{_username}|{_currentChannelId}"); + } + public async void WriteRtcOffer(string json) { try @@ -391,21 +453,33 @@ public partial class MainPage : ContentPage RtcDescription? offer = await ServerAPI.GetOffersForChannelAsync(_currentChannelId); return JsonSerializer.Serialize(offer); } + public async void WriteRtcAnswer(string json) { - RtcDescription? description = JsonSerializer.Deserialize(json); - DBOffer answer = new DBOffer + SafeSendRawToWebView("WriteRtcAnswer entered with: " + json); + + try { - ChannelId = _currentChannelId, - Username = _username, - SessionDescription = description - }; - await ServerAPI.PostAnswerAsync(answer); + 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 AnswerCallback(RtcDescription answer) { - await hybridWebView.EvaluateJavaScriptAsync($"window.AnswerCallback({answer})"); + var json = JsonSerializer.Serialize(answer); + await hybridWebView.EvaluateJavaScriptAsync($"window.AnswerCallback({json})"); } private void OnSendMessageButtonClicked(object sender, EventArgs e) diff --git a/RelayClient/Models/RtcNotificationMessage.cs b/RelayClient/Models/RtcNotificationMessage.cs new file mode 100644 index 0000000..519ee34 --- /dev/null +++ b/RelayClient/Models/RtcNotificationMessage.cs @@ -0,0 +1,9 @@ +namespace RelayClient.Models; + +public sealed class RtcNotificationMessage +{ + public string? Type { get; set; } + public string? ChannelId { get; set; } + public string? Username { get; set; } + public string? Direction { get; set; } +} \ No newline at end of file diff --git a/RelayClient/Resources/Raw/wwwroot/index.js b/RelayClient/Resources/Raw/wwwroot/index.js index a9d27eb..806ff76 100644 --- a/RelayClient/Resources/Raw/wwwroot/index.js +++ b/RelayClient/Resources/Raw/wwwroot/index.js @@ -231,8 +231,10 @@ async function channelCallJoin(activeCall) const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); - LogMessage(`Joining call with media answer: ${JSON.stringify(answer)}`); - await window.HybridWebView.InvokeDotNet("WriteRtcAnswer", [JSON.stringify(roomAnswer)]); + LogMessage("Joining call with media answer: " + JSON.stringify(answer)); + LogMessage("Calling C# WriteRtcAnswer with: " + JSON.stringify(answer)); + await window.HybridWebView.InvokeDotNet("WriteRtcAnswer", [JSON.stringify(answer)]); + LogMessage("C# WriteRtcAnswer invoked"); //TODO: Update offer in SurrealDB to include answer } else diff --git a/RelayClient/ServerAPI.cs b/RelayClient/ServerAPI.cs index d9699ea..75361eb 100644 --- a/RelayClient/ServerAPI.cs +++ b/RelayClient/ServerAPI.cs @@ -45,9 +45,14 @@ public class ServerAPI return offer; } - public static async Task PostAnswerAsync(DBOffer answer) + public static async Task PostAnswerAsync(DBOffer answer) { HttpResponseMessage response = await client.PostAsJsonAsync("api/rtc/answer", answer); + var body = await response.Content.ReadAsStringAsync(); + + Console.WriteLine("PostAnswerAsync status: " + response.StatusCode); + Console.WriteLine("PostAnswerAsync body: " + body); + response.EnsureSuccessStatusCode(); return response.Headers.Location; } @@ -94,6 +99,18 @@ public class ServerAPI return response.Headers.Location; } + public static async Task GetAnswerForChannelAsync(string? channelId) + { + if (string.IsNullOrWhiteSpace(channelId)) + return null; + + HttpResponseMessage response = await client.GetAsync($"api/rtc/answer/{channelId}"); + if (!response.IsSuccessStatusCode) + return null; + + var json = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(json); + } } public class RtcDescription diff --git a/RelayServer/Endpoints/RtcEndpoints.cs b/RelayServer/Endpoints/RtcEndpoints.cs index 29dba1d..7bfbde8 100644 --- a/RelayServer/Endpoints/RtcEndpoints.cs +++ b/RelayServer/Endpoints/RtcEndpoints.cs @@ -49,15 +49,11 @@ public static class RtcEndpoints // Store a new SDP answer for the specified channel call. app.MapPost("/api/rtc/answer", async (RtcOffer request, RtcCallService rtcCallService) => { + Console.WriteLine($"RTC answer received for channel {request.ChannelId} from {request.Username}"); + await rtcCallService.WriteAnswerAsync(request.ChannelId, request.SessionDescription); - //DON'T FUCKING HARDCODE STRINGS INTO API REQUESTS - // await rtcCallService.WriteAnswerAsync( - // request.ChannelId, - // new RtcSessionDescription - // { - // Type = "answer", - // Sdp = request.Sdp - // }); + + Console.WriteLine($"Broadcasting rtc_answer_updated for {request.ChannelId}"); RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage { diff --git a/RelayServer/Services/Rtc/RtcCallService.cs b/RelayServer/Services/Rtc/RtcCallService.cs index a47b307..f92330b 100644 --- a/RelayServer/Services/Rtc/RtcCallService.cs +++ b/RelayServer/Services/Rtc/RtcCallService.cs @@ -124,13 +124,14 @@ public sealed class RtcCallService /// /// A list of answers for the channel ordered from oldest to newest. /// - public async Task> GetAnswersAsync(string channelId) + public async Task> GetAnswersAsync(string channelId) { - var answers = await _db.Select("rtc_answers"); - return answers - .Where(x => x.ChannelId == channelId) - .OrderBy(x => x.CreatedAt) - .ToList(); + var activeCall = await GetActiveCallAsync(channelId); + + if (activeCall?.Answer is null) + return []; + + return [activeCall.Answer]; } /// @@ -140,13 +141,10 @@ public sealed class RtcCallService /// /// The newest answer for the channel, or null if no answer exists. /// - public async Task GetLatestAnswerAsync(string channelId) + public async Task GetLatestAnswerAsync(string channelId) { - var answers = await _db.Select("rtc_answers"); - return answers - .Where(x => x.ChannelId == channelId) - .OrderByDescending(x => x.CreatedAt) - .FirstOrDefault(); + var activeCall = await GetActiveCallAsync(channelId); + return activeCall?.Answer; } ///