Encryption Sent, Encrpytion Decoded, Offer Sent, Offer Recieved, JS -> C# / C# -> JS Broke (some disconnect here) SendRtcSignalToJsAsync

This commit is contained in:
2026-04-24 05:18:50 -04:00
parent 0c9ff3b5d9
commit a52ae2f4a4
5 changed files with 175 additions and 74 deletions

View File

@@ -190,6 +190,20 @@ public partial class MainPage : ContentPage
privateKey privateKey
); );
var rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(decryptedJson);
if (rtcSignal is null)
return;
if (!string.IsNullOrWhiteSpace(rtcSignal.To) &&
!string.Equals(rtcSignal.To, _username, StringComparison.OrdinalIgnoreCase))
{
SafeSendRawToWebView($"Ignoring RTC signal meant for {rtcSignal.To}");
return;
}
SafeSendRawToWebView("Received encrypted RTC signal: " + decryptedJson);
MainThread.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
await SendRtcSignalToJsAsync(decryptedJson); await SendRtcSignalToJsAsync(decryptedJson);
@@ -438,20 +452,20 @@ public partial class MainPage : ContentPage
} }
#region RTC Functions #region RTC Functions
public async Task<bool> JoinRtcChannel() public async Task JoinRtcChannel()
{ {
if (string.IsNullOrWhiteSpace(_currentChannelId)) if (string.IsNullOrWhiteSpace(_currentChannelId))
return false; return; //false;
_wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}"); _wsc.Send($"RTC_JOIN_CHANNEL|{_username}|{_currentChannelId}");
// SafeSendRawToWebView($"Attempting to join RTC Channel {_currentChannelName} | {_currentChannelId} "); // SafeSendRawToWebView($"Attempting to join RTC Channel {_currentChannelName} | {_currentChannelId} ");
bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId); //bool active = await ServerAPI.GetIsChannelActiveAsync(_currentChannelId);
SafeSendRawToWebView($"Rtc Channel {_currentChannelName} | {_currentChannelId} is active: {active}"); //SafeSendRawToWebView($"Rtc Channel {_currentChannelName} | {_currentChannelId} is active: {active}");
return active; return; //active;
} }
public void LeaveRtcChannel() public void LeaveRtcChannel()
@@ -566,10 +580,36 @@ public partial class MainPage : ContentPage
} }
} }
private async Task SendRtcSignalToJsAsync(string rawJson) private Task SendRtcSignalToJsAsync(string rawJson)
{ {
MainThread.BeginInvokeOnMainThread(async () =>
{
try
{
SafeSendRawToWebView("Dispatching RTC signal to JS");
var jsArg = JsonSerializer.Serialize(rawJson); var jsArg = JsonSerializer.Serialize(rawJson);
await hybridWebView.EvaluateJavaScriptAsync($"window.handleRtcSignal({jsArg})");
await hybridWebView.EvaluateJavaScriptAsync($@"
window.HybridWebView.SendRawMessage('JS dispatch wrapper hit');
const fn = window.handleRtcSignal || window.dispatchRtcSignal;
if (!fn) {{
window.HybridWebView.SendRawMessage('No RTC signal handler found on window');
}} else {{
window.HybridWebView.SendRawMessage('Calling RTC signal handler');
fn({jsArg});
}}
");
SafeSendRawToWebView("RTC signal dispatched to JS");
}
catch (Exception ex)
{
SafeSendRawToWebView("SendRtcSignalToJsAsync failed: " + ex.Message);
}
});
return Task.CompletedTask;
} //Remove? } //Remove?
private async Task PushRtcContextToJsAsync() private async Task PushRtcContextToJsAsync()
@@ -585,26 +625,40 @@ public partial class MainPage : ContentPage
public void SendRtcSignal(string json) public void SendRtcSignal(string json)
{ {
SafeSendRawToWebView("SendRtcSignal entered: " + json);
if (string.IsNullOrWhiteSpace(_serverPublicKey)) if (string.IsNullOrWhiteSpace(_serverPublicKey))
{ {
Console.WriteLine("Server public key not loaded yet."); SafeSendRawToWebView("SendRtcSignal failed: server public key not loaded.");
return; return;
} }
RtcSignalMessage? rtcSignal; RtcSignalMessage? rtcSignal;
try try
{ {
rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(json); rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(json);
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Failed to parse RTC signal from JS: {ex.Message}"); SafeSendRawToWebView("SendRtcSignal failed to parse RTC signal: " + ex.Message);
return; return;
} }
if (rtcSignal is null) if (rtcSignal is null)
{
SafeSendRawToWebView("SendRtcSignal failed: rtcSignal was null.");
return; return;
}
if (string.IsNullOrWhiteSpace(rtcSignal.ChannelId))
{
SafeSendRawToWebView("SendRtcSignal failed: channelId was empty.");
return;
}
try
{
var encrypted = E2EeHelper.EncryptForRecipient(json, _serverPublicKey); var encrypted = E2EeHelper.EncryptForRecipient(json, _serverPublicKey);
var payload = new SocketRtcSignalMessage var payload = new SocketRtcSignalMessage
@@ -618,8 +672,16 @@ public partial class MainPage : ContentPage
EncryptedKey = encrypted.EncryptedKey EncryptedKey = encrypted.EncryptedKey
}; };
_wsc.Send(JsonSerializer.Serialize(payload)); var socketJson = JsonSerializer.Serialize(payload);
_wsc.Send(socketJson);
SafeSendRawToWebView($"SendRtcSignal sent: {rtcSignal.Type} -> {rtcSignal.ChannelId}");
Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}"); Console.WriteLine($"[{_username}] sent RTC signal: {rtcSignal.Type} -> {rtcSignal.ChannelId}");
}
catch (Exception ex)
{
SafeSendRawToWebView("SendRtcSignal websocket/encrypt failed: " + ex.Message);
}
} //Remove? } //Remove?
public async Task<string> GetRtcParticipants() public async Task<string> GetRtcParticipants()

View File

@@ -164,27 +164,24 @@ async function joinChannelCall() {
LogMessage("Current username: " + currentUsername); LogMessage("Current username: " + currentUsername);
LogMessage("Current channel: " + currentChannelId); LogMessage("Current channel: " + currentChannelId);
const isActive = await window.HybridWebView.InvokeDotNet("JoinRtcChannel"); await window.HybridWebView.InvokeDotNet("JoinRtcChannel");
const peerConnection = await ensurePeerConnectionForUser(currentUsername);
await ensureLocalMedia(); await ensureLocalMedia();
if (isActive) {
const rawJson = await window.HybridWebView.InvokeDotNet("GetRtcOffer");
const offer = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
await peerConnection.setRemoteDescription(offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setRemoteDescription(answer);
await window.HybridWebView.InvokeDotNet("WriteRtcAnswer", [JSON.stringify(answer)])
const rawParticipants = await window.HybridWebView.InvokeDotNet("GetRtcParticipants"); const rawParticipants = await window.HybridWebView.InvokeDotNet("GetRtcParticipants");
const participants = typeof rawParticipants === "string" ? JSON.parse(rawParticipants) : rawParticipants; const participants = typeof rawParticipants === "string"
? JSON.parse(rawParticipants)
: rawParticipants;
LogMessage("Participants: " + JSON.stringify(participants)); // TODO: Remove LogMessage("Participants: " + JSON.stringify(participants));
for (const username of participants) { const otherUsers = participants.filter(username => username !== currentUsername);
if (username === currentUsername) continue;
if (otherUsers.length === 0) {
LogMessage("Joined call as first participant. Waiting for others...");
return;
}
for (const username of otherUsers) {
const pc = await ensurePeerConnectionForUser(username); const pc = await ensurePeerConnectionForUser(username);
const offer = await pc.createOffer(); const offer = await pc.createOffer();
@@ -195,27 +192,15 @@ async function joinChannelCall() {
from: currentUsername, from: currentUsername,
to: username, to: username,
channelId: currentChannelId, channelId: currentChannelId,
sdp: offer.sdp sdp: offer.sdp,
isInitiator: true
}; };
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]); await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
LogMessage(`Sent offer to ${username}`); LogMessage(`Sent offer to ${username}`);
} }
}
else
{
try {
LogMessage(currentUsername + " attempted to join inactive channel. Making new call.")
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
await window.HybridWebView.InvokeDotNet("WriteRtcOffer", [JSON.stringify(offer)]);
LogMessage(`Joining call with media offer: ${JSON.stringify(offer)}`);
}
catch (error) {
LogMessage(error)
}
}
} }
async function channelCallJoin(activeCall) async function channelCallJoin(activeCall)
{ {
// LogMessage("Active call: " + activeCall); // LogMessage("Active call: " + activeCall);
@@ -337,6 +322,11 @@ async function handleRtcSignal(rawJson) {
if (!msg.from || msg.from === currentUsername) return; if (!msg.from || msg.from === currentUsername) return;
if (msg.to && msg.to !== currentUsername) {
LogMessage(`Ignoring signal meant for ${msg.to}`);
return;
}
const pc = await ensurePeerConnectionForUser(msg.from); const pc = await ensurePeerConnectionForUser(msg.from);
if (msg.type === "rtc_offer") { if (msg.type === "rtc_offer") {
@@ -390,6 +380,8 @@ async function handleRtcSignal(rawJson) {
} }
} }
window.handleRtcSignal = handleRtcSignal;
async function loadDevices() { async function loadDevices() {
try { try {
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
@@ -510,8 +502,6 @@ function removeRemoteTile(username) {
} }
} }
window.handleRtcSignal = handleRtcSignal;
window.addEventListener("HybridWebViewMessageReceived", function (e) { window.addEventListener("HybridWebViewMessageReceived", function (e) {
LogMessage("Raw message: " + e.detail.message); LogMessage("Raw message: " + e.detail.message);
}); });

View File

@@ -122,6 +122,27 @@ public class ChatSocketBehavior : WebSocketBehavior
return; return;
} }
string plainText;
try
{
plainText = E2EeHelper.DecryptForRecipient(
new EncryptedPayload
{
CipherText = clientPayload.CipherText,
Nonce = clientPayload.Nonce,
Tag = clientPayload.Tag,
EncryptedKey = clientPayload.EncryptedKey
},
ServerPrivateKey
);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to decrypt RTC signal: {ex.Message}");
return;
}
var sessionIds = RtcChannelPresenceService.GetSessionsInChannel(clientPayload.ChannelId); var sessionIds = RtcChannelPresenceService.GetSessionsInChannel(clientPayload.ChannelId);
foreach (var sessionId in sessionIds) foreach (var sessionId in sessionIds)
@@ -129,7 +150,28 @@ public class ChatSocketBehavior : WebSocketBehavior
if (sessionId == ID) if (sessionId == ID)
continue; continue;
Sessions.SendTo(JsonSerializer.Serialize(clientPayload), sessionId); var username = RtcChannelPresenceService.GetUsernameForSession(sessionId);
if (string.IsNullOrWhiteSpace(username))
continue;
var clientKey = GetClientPublicKeyByUsernameSync(username);
if (clientKey is null)
continue;
var encrypted = E2EeHelper.EncryptForRecipient(plainText, clientKey.PublicKey);
var outbound = new SocketRtcSignalMessage
{
Type = SignalType.EncryptedSignal,
SenderUsername = clientPayload.SenderUsername,
ChannelId = clientPayload.ChannelId,
CipherText = encrypted.CipherText,
Nonce = encrypted.Nonce,
Tag = encrypted.Tag,
EncryptedKey = encrypted.EncryptedKey
};
Sessions.SendTo(JsonSerializer.Serialize(outbound), sessionId);
} }
Console.WriteLine($"Forwarded encrypted RTC signal from {clientPayload.SenderUsername} to channel {clientPayload.ChannelId}"); Console.WriteLine($"Forwarded encrypted RTC signal from {clientPayload.SenderUsername} to channel {clientPayload.ChannelId}");

View File

@@ -57,4 +57,11 @@ public static class RtcChannelPresenceService
return SessionToChannel.TryGetValue(sessionId, out var currentChannel) && return SessionToChannel.TryGetValue(sessionId, out var currentChannel) &&
string.Equals(currentChannel, channelId, StringComparison.Ordinal); string.Equals(currentChannel, channelId, StringComparison.Ordinal);
} }
public static string? GetUsernameForSession(string sessionId)
{
return SessionToUsername.TryGetValue(sessionId, out var username)
? username
: null;
}
} }

View File

@@ -37,7 +37,7 @@ public enum ChannelType
public sealed class RtcSignalMessage public sealed class RtcSignalMessage
{ {
[JsonPropertyName("type")] [JsonPropertyName("type")]
public SignalType Type { get; set; } public string Type { get; set; } = string.Empty;
[JsonPropertyName("from")] [JsonPropertyName("from")]
public string From { get; set; } = string.Empty; public string From { get; set; } = string.Empty;