Messaging works, let's start there.

This commit is contained in:
2026-04-13 16:12:02 -04:00
parent 085507519a
commit 0375693970
7 changed files with 645 additions and 820 deletions

View File

@@ -115,11 +115,9 @@ public partial class MainPage : ContentPage
using var doc = JsonDocument.Parse(e.Data);
var root = doc.RootElement;
if (!root.TryGetProperty("Type", out var typeElement))
if (!TryReadSignalType(root, out var type))
return;
var type = (SignalType) typeElement.GetInt32();
if (type == SignalType.ChannelList)
{
var channelList = JsonSerializer.Deserialize<SocketChannelList>(e.Data);
@@ -218,25 +216,46 @@ public partial class MainPage : ContentPage
{
case SignalType.OfferUpdated :
{
var offer = await GetRtcOffer();
await SendRtcSignalToJsAsync(offer);
if (!string.Equals(rtcNotification.TargetUsername, _username, StringComparison.OrdinalIgnoreCase))
break;
if (string.IsNullOrWhiteSpace(_currentChannelId) || string.IsNullOrWhiteSpace(rtcNotification.Username))
break;
var offer = await ServerAPI.GetOfferForChannelAsync(_currentChannelId, rtcNotification.Username, _username);
if (offer is not null)
{
await SendRtcOfferToJsAsync(rtcNotification.Username, offer);
}
break;
}
case SignalType.AnswerUpdated:
{
var answer = await ServerAPI.GetAnswerForChannelAsync(_currentChannelId);
if (!string.Equals(rtcNotification.TargetUsername, _username, StringComparison.OrdinalIgnoreCase))
break;
if (string.IsNullOrWhiteSpace(_currentChannelId) || string.IsNullOrWhiteSpace(rtcNotification.Username))
break;
var answer = await ServerAPI.GetAnswerForChannelAsync(_currentChannelId, rtcNotification.Username, _username);
if (answer is not null)
{
await AnswerCallback(answer);
await SendRtcAnswerToJsAsync(rtcNotification.Username, answer);
}
break;
}
case SignalType.CandidateAdded:
{
if (!string.Equals(rtcNotification.TargetUsername, _username, StringComparison.OrdinalIgnoreCase))
break;
try
{
IceCandidate? iceCandidate = JsonSerializer.Deserialize<IceCandidate>(rtcNotification.Direction);
IceCandidateCallback(iceCandidate);
if (iceCandidate is not null && !string.IsNullOrWhiteSpace(rtcNotification.Username))
{
await SendRtcCandidateToJsAsync(rtcNotification.Username, iceCandidate);
}
}
catch (Exception ex)
{
@@ -248,7 +267,10 @@ public partial class MainPage : ContentPage
case SignalType.CallLeft:
{
SafeSendRawToWebView("RTC call left notification received.");
RtcLeaveCallback();
if (!string.IsNullOrWhiteSpace(rtcNotification.Username))
{
RtcLeaveCallback(rtcNotification.Username);
}
break;
}
}
@@ -264,7 +286,7 @@ public partial class MainPage : ContentPage
if (pyload is null)
return;
if (pyload.RecipientUsername == _username)
if (!string.Equals(pyload.RecipientUsername, _username, StringComparison.OrdinalIgnoreCase))
return;
Console.WriteLine($"[{_username}] received encrypted payload for {pyload.RecipientUsername}");
@@ -417,20 +439,24 @@ public partial class MainPage : ContentPage
}
#region RTC Functions
public async Task<bool> JoinRtcChannel()
public async Task<string> JoinRtcChannel()
{
if (string.IsNullOrWhiteSpace(_currentChannelId))
return false;
return "[]";
_wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}");
SafeSendRawToWebView($"Attempting to join RTC Channel {_currentChannelName} | {_currentChannelId} ");
bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId);
var participants = await ServerAPI.GetParticipantsForChannelAsync(_currentChannelId);
var otherParticipants = participants
.Where(x => !string.Equals(x, _username, StringComparison.OrdinalIgnoreCase))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
SafeSendRawToWebView($"Rtc Channel {_currentChannelName} | {_currentChannelId} is active: {active}");
SafeSendRawToWebView($"RTC participants in {_currentChannelName}: {string.Join(", ", otherParticipants)}");
return active;
return JsonSerializer.Serialize(otherParticipants);
}
public void LeaveRtcChannel()
@@ -441,17 +467,14 @@ public partial class MainPage : ContentPage
_wsc.Send($"RTC_LEAVE_CHANNEL|{_username}|{_currentChannelId}");
}
public async void WriteRtcOffer(string json)
public async Task WriteRtcOffer(string json)
{
try
{
RtcDescription? description = JsonSerializer.Deserialize<RtcDescription>(json);
DBOffer offer = new DBOffer
{
ChannelId = _currentChannelId,
Username = _username,
SessionDescription = description
};
RtcOffer? offer = JsonSerializer.Deserialize<RtcOffer>(json);
if (offer is null)
return;
await ServerAPI.PostOfferAsync(offer);
}
catch (Exception ex)
@@ -460,25 +483,15 @@ public partial class MainPage : ContentPage
}
}
public async Task<string> GetRtcOffer()
{
RtcDescription? offer = await ServerAPI.GetOffersForChannelAsync(_currentChannelId);
return JsonSerializer.Serialize(offer);
}
public async void WriteRtcAnswer(string json)
public async Task 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
};
RtcAnswer? answer = JsonSerializer.Deserialize<RtcAnswer>(json);
if (answer is null)
return;
await ServerAPI.PostAnswerAsync(answer);
SafeSendRawToWebView("WriteRtcAnswer posted successfully");
}
@@ -488,19 +501,15 @@ public partial class MainPage : ContentPage
}
}
public async void WriteIceCandidate(string json)
public async Task 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);
DBIceCandidate? dbCandidate = JsonSerializer.Deserialize<DBIceCandidate>(json);
if (dbCandidate is null)
return;
await ServerAPI.PostIceCandidateAsync(dbCandidate);
}
catch (Exception ex)
{
@@ -508,47 +517,45 @@ public partial class MainPage : ContentPage
}
}
public async void IceCandidateCallback(IceCandidate candidate)
private async Task SendRtcOfferToJsAsync(string remoteUsername, RtcSessionDescription offer)
{
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);
}
var remoteUsernameJson = JsonSerializer.Serialize(remoteUsername);
var offerJson = JsonSerializer.Serialize(offer);
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcOffer({remoteUsernameJson}, {offerJson})");
}
public async void RtcLeaveCallback()
private async Task SendRtcAnswerToJsAsync(string remoteUsername, RtcSessionDescription answer)
{
var remoteUsernameJson = JsonSerializer.Serialize(remoteUsername);
var answerJson = JsonSerializer.Serialize(answer);
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcAnswer({remoteUsernameJson}, {answerJson})");
}
private async Task SendRtcCandidateToJsAsync(string remoteUsername, IceCandidate candidate)
{
var remoteUsernameJson = JsonSerializer.Serialize(remoteUsername);
var candidateJson = JsonSerializer.Serialize(candidate);
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcCandidate({remoteUsernameJson}, {candidateJson})");
}
public async void RtcLeaveCallback(string username)
{
try
{
await hybridWebView.InvokeJavaScriptAsync("RtcLeaveCall", [], []);
var usernameJson = JsonSerializer.Serialize(username);
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcParticipantLeft({usernameJson})");
}
catch (Exception ex)
{
SafeSendRawToWebView("RtcLeaveCallback failed: " + ex.Message);
}
}
private async Task SendRtcSignalToJsAsync(string rawJson)
{
var jsArg = JsonSerializer.Serialize(rawJson);
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal({jsArg})");
} //Remove?
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal?.({jsArg})");
}
private async Task PushRtcContextToJsAsync()
{
@@ -634,15 +641,50 @@ public partial class MainPage : ContentPage
}
[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(RtcDescription))]
[JsonSerializable(typeof(List<RtcSignalMessage>))]
[JsonSerializable(typeof(RtcSessionDescription))]
[JsonSerializable(typeof(IceCandidate))]
[JsonSerializable(typeof(List<IceCandidate>))]
[JsonSerializable(typeof(string))]
internal partial class HybridJSType : JsonSerializerContext
{
// This type's attributes specify JSON serialization info to preserve type structure
// for trimmed builds.
}
}
private static bool TryReadSignalType(JsonElement root, out SignalType type)
{
if (TryGetProperty(root, "type", out var typeElement))
{
if (typeElement.ValueKind == JsonValueKind.String &&
Enum.TryParse(typeElement.GetString(), true, out SignalType parsedType))
{
type = parsedType;
return true;
}
if (typeElement.ValueKind == JsonValueKind.Number &&
typeElement.TryGetInt32(out var rawValue))
{
type = (SignalType)rawValue;
return true;
}
}
type = default;
return false;
}
private static bool TryGetProperty(JsonElement root, string propertyName, out JsonElement value)
{
foreach (var property in root.EnumerateObject())
{
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
{
value = property.Value;
return true;
}
}
value = default;
return false;
}
}