Compare commits

..

2 Commits

Author SHA1 Message Date
f77a5eb823 Merge remote-tracking branch 'origin/RTC-Rewrite' into RTC-Rewrite 2026-04-27 06:49:55 -04:00
d6ecb63b5f MainPage cleanup 2026-04-27 06:49:44 -04:00

View File

@@ -18,7 +18,7 @@ public partial class MainPage : ContentPage
private readonly Dictionary<string, List<ChatMessage>> _messagesByChannel = new(); private readonly Dictionary<string, List<ChatMessage>> _messagesByChannel = new();
private readonly List<ChannelItem> _channels = []; private readonly List<ChannelItem> _channels = [];
public MainPage(string username) public MainPage(string username)
{ {
InitializeComponent(); InitializeComponent();
@@ -34,7 +34,7 @@ public partial class MainPage : ContentPage
} }
_wsc = new WebSocket("ws://localhost:1337/"); _wsc = new WebSocket("ws://localhost:1337/");
_wsc.OnMessage += WscOnMessage; _wsc.OnMessage += WscOnMessage;
_wsc.Connect(); _wsc.Connect();
@@ -42,7 +42,7 @@ public partial class MainPage : ContentPage
_wsc.Send($"REGISTER_KEY|{_username}|{publicKey}"); _wsc.Send($"REGISTER_KEY|{_username}|{publicKey}");
_wsc.Send("GET_SERVER_KEY"); _wsc.Send("GET_SERVER_KEY");
_wsc.Send("GET_CHANNELS"); _wsc.Send("GET_CHANNELS");
hybridWebView.SetInvokeJavaScriptTarget(this); hybridWebView.SetInvokeJavaScriptTarget(this);
ServerAPI.setupClient(); ServerAPI.setupClient();
@@ -70,7 +70,7 @@ public partial class MainPage : ContentPage
Console.WriteLine("Server public key not loaded yet."); Console.WriteLine("Server public key not loaded yet.");
return; return;
} }
if (string.IsNullOrWhiteSpace(_currentChannelId)) if (string.IsNullOrWhiteSpace(_currentChannelId))
{ {
Console.WriteLine("No channel selected yet."); Console.WriteLine("No channel selected yet.");
@@ -106,9 +106,9 @@ public partial class MainPage : ContentPage
Console.WriteLine(e.Data); Console.WriteLine(e.Data);
return; return;
} }
// SafeSendRawToWebView($"[{_username}] RAW WS DATA: {e.Data}"); // SafeSendRawToWebView($"[{_username}] RAW WS DATA: {e.Data}");
Console.WriteLine($"[{_username}] RAW WS DATA: {e.Data}"); Console.WriteLine($"[{_username}] RAW WS DATA: {e.Data}");
try try
@@ -119,7 +119,7 @@ public partial class MainPage : ContentPage
if (!root.TryGetProperty("Type", out var typeElement)) if (!root.TryGetProperty("Type", out var typeElement))
return; return;
var type = (SignalType) typeElement.GetInt32(); var type = (SignalType)typeElement.GetInt32();
if (type == SignalType.ChannelList) if (type == SignalType.ChannelList)
{ {
@@ -165,7 +165,7 @@ public partial class MainPage : ContentPage
return; return;
} }
if (type == SignalType.EncryptedSignal) if (type == SignalType.EncryptedSignal)
{ {
var payload = JsonSerializer.Deserialize<SocketRtcSignalMessage>(e.Data); var payload = JsonSerializer.Deserialize<SocketRtcSignalMessage>(e.Data);
@@ -190,7 +190,7 @@ public partial class MainPage : ContentPage
}, },
privateKey privateKey
); );
var rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(decryptedJson); var rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(decryptedJson);
if (rtcSignal is null) if (rtcSignal is null)
@@ -212,76 +212,7 @@ public partial class MainPage : ContentPage
return; return;
} }
if (type is SignalType.OfferUpdated or SignalType.AnswerUpdated or SignalType.CandidateAdded or SignalType.CallLeft)
{
var rtcNotification = JsonSerializer.Deserialize<RtcNotificationMessage>(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<IceCandidate>(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) if (type != SignalType.EncryptedChat)
return; return;
@@ -361,14 +292,14 @@ public partial class MainPage : ContentPage
{ {
_currentChannelId = channel.ChannelId; _currentChannelId = channel.ChannelId;
_currentChannelName = channel.Name; _currentChannelName = channel.Name;
MainThread.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
await PushRtcContextToJsAsync(); await PushRtcContextToJsAsync();
if (channel.Type == ChannelType.Voice) if (channel.Type == ChannelType.Voice)
{ {
SwapView(); 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
} }
}); });
@@ -447,28 +378,23 @@ public partial class MainPage : ContentPage
ViewSwapped.Text = "Swap to Message View"; ViewSwapped.Text = "Swap to Message View";
} }
} }
private void SwapView_OnClicked(object? sender, EventArgs e) private void SwapView_OnClicked(object? sender, EventArgs e)
{ {
SwapView(); SwapView();
} }
#region RTC Functions #region RTC Functions
public async Task JoinRtcChannel()
public Task JoinRtcChannel()
{ {
if (string.IsNullOrWhiteSpace(_currentChannelId)) if (string.IsNullOrWhiteSpace(_currentChannelId))
return; //false; return Task.CompletedTask;
_wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}"); _wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}");
return Task.CompletedTask;
// 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() public void LeaveRtcChannel()
{ {
if (string.IsNullOrWhiteSpace(_currentChannelId)) if (string.IsNullOrWhiteSpace(_currentChannelId))
@@ -476,151 +402,9 @@ public partial class MainPage : ContentPage
_wsc.Send($"RTC_LEAVE_CHANNEL|{_username}|{_currentChannelId}"); _wsc.Send($"RTC_LEAVE_CHANNEL|{_username}|{_currentChannelId}");
} }
public async void WriteRtcOffer(string json)
{
try
{
RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(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<string> 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<RtcDescription>(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<IceCandidate>(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) public void SendRtcSignal(string json)
{ {
SafeSendRawToWebView("SendRtcSignal entered: " + json);
if (string.IsNullOrWhiteSpace(_serverPublicKey)) if (string.IsNullOrWhiteSpace(_serverPublicKey))
{ {
SafeSendRawToWebView("SendRtcSignal failed: server public key not loaded."); SafeSendRawToWebView("SendRtcSignal failed: server public key not loaded.");
@@ -640,20 +424,25 @@ public partial class MainPage : ContentPage
} }
if (rtcSignal is null) if (rtcSignal is null)
{
SafeSendRawToWebView("SendRtcSignal failed: rtcSignal was null.");
return; return;
}
if (string.IsNullOrWhiteSpace(rtcSignal.ChannelId))
rtcSignal.ChannelId = _currentChannelId;
if (string.IsNullOrWhiteSpace(rtcSignal.From))
rtcSignal.From = _username;
if (string.IsNullOrWhiteSpace(rtcSignal.ChannelId)) if (string.IsNullOrWhiteSpace(rtcSignal.ChannelId))
{ {
SafeSendRawToWebView("SendRtcSignal failed: channelId was empty."); SafeSendRawToWebView("SendRtcSignal failed: missing channel id.");
return; return;
} }
var outgoingJson = JsonSerializer.Serialize(rtcSignal);
try try
{ {
var encrypted = E2EeHelper.EncryptForRecipient(json, _serverPublicKey); var encrypted = E2EeHelper.EncryptForRecipient(outgoingJson, _serverPublicKey);
var payload = new SocketRtcSignalMessage var payload = new SocketRtcSignalMessage
{ {
@@ -666,30 +455,72 @@ public partial class MainPage : ContentPage
EncryptedKey = encrypted.EncryptedKey EncryptedKey = encrypted.EncryptedKey
}; };
var socketJson = JsonSerializer.Serialize(payload); _wsc.Send(JsonSerializer.Serialize(payload));
_wsc.Send(socketJson);
SafeSendRawToWebView($"SendRtcSignal sent: {rtcSignal.Type} -> {rtcSignal.ChannelId}"); SafeSendRawToWebView($"SendRtcSignal sent: {rtcSignal.Type} -> {rtcSignal.To}");
Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}");
} }
catch (Exception ex) catch (Exception ex)
{ {
SafeSendRawToWebView("SendRtcSignal websocket/encrypt failed: " + ex.Message); SafeSendRawToWebView("SendRtcSignal failed: " + ex.Message);
} }
} //Remove? }
public async Task<string> GetRtcParticipants() public async Task<string> GetRtcParticipants()
{ {
if (string.IsNullOrWhiteSpace(_currentChannelId))
return "[]";
var participants = await ServerAPI.GetRtcParticipantsAsync(_currentChannelId); var participants = await ServerAPI.GetRtcParticipantsAsync(_currentChannelId);
return JsonSerializer.Serialize(participants); return JsonSerializer.Serialize(participants ?? []);
} }
#endregion private Task SendRtcSignalToJsAsync(string rawJson)
private void OnSendMessageButtonClicked(object sender, EventArgs e)
{ {
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) private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e)
{ {
if (e.Message == "rtc_page_ready") if (e.Message == "rtc_page_ready")