Summary Update.

This commit is contained in:
2026-06-06 23:38:50 -04:00
parent dd75ca4b06
commit 2916d17868
30 changed files with 1231 additions and 21 deletions

View File

@@ -6,14 +6,39 @@ using RelayShared.Services;
namespace RelayClient.Services;
/// <summary>
/// The bridge between the C# WebSocket pipe and the JavaScript WebRTC engine
/// running inside the HybridWebView (which is shown when a Voice channel is open).
///
/// Outbound (JS → C# → server): the WebView JS calls into C# via SendRtcSignal(json).
/// We deserialise to RtcSignalMessage, encrypt with the server's public key, wrap in
/// SocketRtcSignalMessage, and send through the WebSocket.
///
/// Inbound (server → C# → JS): the WebSocket fires EncryptedRtcSignalReceived. MainPage
/// hands it to HandleIncomingRtcSignalAsync, which decrypts with the user's private key
/// and calls back into JS via hybridWebView.InvokeJavaScriptAsync("testIndex", …).
///
/// JoinRtcChannel / LeaveRtcChannel just send WsAction control messages; presence tracking
/// happens server-side in RtcChannelPresenceService.
/// </summary>
public sealed class RtcBridgeService
{
/// <summary>The currently-signed-in username. Stamped onto outgoing RTC signals.</summary>
private readonly string _username;
/// <summary>The shared WebSocket to RelayServer. Outbound RTC signals ride on this.</summary>
private readonly RelaySocketClient _socket;
/// <summary>The MAUI HybridWebView that hosts the JS WebRTC engine. We push JS calls into it.</summary>
private readonly HybridWebView _hybridWebView;
/// <summary>Lazy view into MainPage._currentChannelId so we always have the current voice channel.</summary>
private readonly Func<string?> _getCurrentChannelId;
/// <summary>Diagnostic logger that surfaces messages back to the WebView UI. Used for status/error reporting.</summary>
private readonly Action<string> _sendRawToWebView;
/// <summary>Captures collaborators. MainPage constructs this once and never replaces it.</summary>
public RtcBridgeService(string username, RelaySocketClient socket, HybridWebView hybridWebView,
Func<string?> getCurrentChannelId, Action<string> sendRawToWebView)
{
@@ -24,6 +49,7 @@ public sealed class RtcBridgeService
_sendRawToWebView = sendRawToWebView;
}
/// <summary>Sends RtcJoin for the currently-selected channel. Server-side, this triggers the Speak permission check and presence registration.</summary>
public Task JoinRtcChannel()
{
var channelId = _getCurrentChannelId();
@@ -35,6 +61,7 @@ public sealed class RtcBridgeService
return Task.CompletedTask;
}
/// <summary>Sends RtcLeave for the currently-selected channel. Clears server-side voice presence so peers stop seeing us.</summary>
public void LeaveRtcChannel()
{
var channelId = _getCurrentChannelId();
@@ -45,6 +72,13 @@ public sealed class RtcBridgeService
_socket.SendRtcLeaveChannel(channelId);
}
/// <summary>
/// Called from JavaScript (via the HybridWebView bridge) when the WebRTC engine wants to
/// send an SDP offer/answer or ICE candidate to other peers. Parses the JSON, fills in
/// missing ChannelId/From, encrypts with the server's public key, ships as
/// SocketRtcSignalMessage. The server then forwards it (re-encrypted per-recipient) to
/// every other session in the same voice channel.
/// </summary>
public void SendRtcSignal(string json)
{
if (string.IsNullOrWhiteSpace(_socket.ServerPublicKey))
@@ -105,6 +139,7 @@ public sealed class RtcBridgeService
}
}
/// <summary>JS bridge: returns the current voice-channel roster as JSON. Hits ServerAPI's REST endpoint, not the WebSocket.</summary>
public async Task<string> GetRtcParticipants()
{
var channelId = _getCurrentChannelId();
@@ -116,6 +151,11 @@ public sealed class RtcBridgeService
return JsonSerializer.Serialize(participants ?? []);
}
/// <summary>
/// MainPage hands incoming SocketRtcSignalMessage frames here. Filters out our own
/// frames, validates the channel scope, decrypts with the user's private key, parses to
/// RtcSignalMessage, then pushes into the JS RTC engine via SendRtcSignalToJsAsync.
/// </summary>
public async Task HandleIncomingRtcSignalAsync(SocketRtcSignalMessage payload)
{
// _sendRawToWebView("HandleIncomingRtcSignal called");
@@ -187,6 +227,10 @@ public sealed class RtcBridgeService
await SendRtcSignalToJsAsync(rtcSignal);
}
/// <summary>
/// Pushes the current username and channelId into JS globals (window.setUsername, window.setChannelId).
/// Called whenever the user switches voice channels OR the JS engine reports rtc_page_ready.
/// </summary>
public Task PushRtcContextToJsAsync()
{
MainThread.BeginInvokeOnMainThread(async () =>
@@ -201,6 +245,11 @@ public sealed class RtcBridgeService
return Task.CompletedTask;
}
/// <summary>
/// Final hop: hands a decrypted RtcSignalMessage off to the JS engine via
/// hybridWebView.InvokeJavaScriptAsync("testIndex", …). SDP strings have their newlines
/// escaped as "(rn)" because the JSON marshalling otherwise breaks them.
/// </summary>
private Task SendRtcSignalToJsAsync(RtcSignalMessage data)
{
if (data.Type == "rtc_offer" || data.Type == "rtc_answer")