Setup new services required for change

This commit is contained in:
2026-04-27 10:01:02 -04:00
parent be797c55c2
commit 1220654656
2 changed files with 355 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
using System.Text.Json;
using RelayClient.Crypto;
using RelayShared.Services;
using WebSocketSharp;
namespace RelayClient.Services;
public sealed class RelaySocketClient
{
private readonly string _username;
private readonly WebSocket _socket;
public string? ServerPublicKey { get; private set; }
public event Action<string>? RawMessageReceived;
public event Action<SocketChannelList>? ChannelListReceived;
public event Action<SocketEncryptedMessage>? EncryptedChatReceived;
public event Action<SocketRtcSignalMessage>? EncryptedRtcSignalReceived;
public event Action<string>? ServerPublicKeyReceived;
public event Action<string>? Log;
public RelaySocketClient(string username, string url = "ws://localhost:1337/")
{
_username = username;
_socket = new WebSocket(url);
_socket.OnMessage += OnMessage;
}
public void Connect()
{
_socket.Connect();
var publicKey = KeyStorage.LoadPublicKey(_username);
SendRaw($"REGISTER_KEY|{_username}|{publicKey}");
SendRaw("GET_SERVER_KEY");
SendRaw("GET_CHANNELS");
}
public void SendRaw(string message)
{
if (_socket.ReadyState == WebSocketState.Open)
_socket.Send(message);
}
public void SendJson<T>(T payload)
{
SendRaw(JsonSerializer.Serialize(payload));
}
public void Disconnect()
{
_socket.OnMessage -= OnMessage;
if (_socket.ReadyState == WebSocketState.Open)
_socket.Close();
}
private void OnMessage(object? sender, MessageEventArgs e)
{
if (e.Data.StartsWith("SERVER:REGISTERED_KEY:"))
{
Log?.Invoke(e.Data);
return;
}
RawMessageReceived?.Invoke(e.Data);
Log?.Invoke($"[{_username}] RAW WS DATA: {e.Data}");
try
{
using var doc = JsonDocument.Parse(e.Data);
var root = doc.RootElement;
if (!root.TryGetProperty("Type", out var typeElement))
return;
var type = (SignalType)typeElement.GetInt32();
switch (type)
{
case SignalType.ChannelList:
{
var channelList = JsonSerializer.Deserialize<SocketChannelList>(e.Data);
if (channelList is not null)
ChannelListReceived?.Invoke(channelList);
return;
}
case SignalType.ServerPublicKey:
{
var serverKeyMessage = JsonSerializer.Deserialize<ServerPublicKeyMessage>(e.Data);
if (serverKeyMessage is not null)
{
ServerPublicKey = serverKeyMessage.PublicKey;
ServerPublicKeyReceived?.Invoke(serverKeyMessage.PublicKey);
}
return;
}
case SignalType.EncryptedSignal:
{
var payload = JsonSerializer.Deserialize<SocketRtcSignalMessage>(e.Data);
if (payload is not null)
EncryptedRtcSignalReceived?.Invoke(payload);
return;
}
case SignalType.EncryptedChat:
{
var payload = JsonSerializer.Deserialize<SocketEncryptedMessage>(e.Data);
if (payload is not null)
EncryptedChatReceived?.Invoke(payload);
return;
}
}
}
catch (Exception ex)
{
Log?.Invoke($"[{_username}] failed to process websocket message: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,228 @@
using System.Text.Json;
using RelayClient.Crypto;
using RelayShared.Rtc;
using RelayShared.Services;
namespace RelayClient.Services;
public sealed class RtcBridgeService
{
private readonly string _username;
private readonly RelaySocketClient _socket;
private readonly HybridWebView _hybridWebView;
private readonly Func<string?> _getCurrentChannelId;
private readonly Action<string> _sendRawToWebView;
public RtcBridgeService(
string username,
RelaySocketClient socket,
HybridWebView hybridWebView,
Func<string?> getCurrentChannelId,
Action<string> sendRawToWebView)
{
_username = username;
_socket = socket;
_hybridWebView = hybridWebView;
_getCurrentChannelId = getCurrentChannelId;
_sendRawToWebView = sendRawToWebView;
}
public Task JoinRtcChannel()
{
var channelId = _getCurrentChannelId();
if (string.IsNullOrWhiteSpace(channelId))
return Task.CompletedTask;
_socket.SendRaw($"RTC_JOIN_CHANNEL|{_username}|{channelId}");
return Task.CompletedTask;
}
public void LeaveRtcChannel()
{
var channelId = _getCurrentChannelId();
if (string.IsNullOrWhiteSpace(channelId))
return;
_socket.SendRaw($"RTC_LEAVE_CHANNEL|{_username}|{channelId}");
}
public void SendRtcSignal(string json)
{
if (string.IsNullOrWhiteSpace(_socket.ServerPublicKey))
{
_sendRawToWebView("SendRtcSignal failed: server public key not loaded.");
return;
}
RtcSignalMessage? rtcSignal;
try
{
rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(json);
}
catch (Exception ex)
{
_sendRawToWebView("SendRtcSignal failed to parse RTC signal: " + ex.Message);
return;
}
if (rtcSignal is null)
return;
rtcSignal.ChannelId ??= _getCurrentChannelId();
rtcSignal.From ??= _username;
if (string.IsNullOrWhiteSpace(rtcSignal.ChannelId))
{
_sendRawToWebView("SendRtcSignal failed: missing channel id.");
return;
}
var outgoingJson = JsonSerializer.Serialize(rtcSignal);
try
{
var encrypted = E2EeHelper.EncryptForRecipient(outgoingJson, _socket.ServerPublicKey);
var payload = new SocketRtcSignalMessage
{
Type = SignalType.EncryptedSignal,
SenderUsername = _username,
ChannelId = rtcSignal.ChannelId,
CipherText = encrypted.CipherText,
Nonce = encrypted.Nonce,
Tag = encrypted.Tag,
EncryptedKey = encrypted.EncryptedKey
};
_socket.SendJson(payload);
_sendRawToWebView($"SendRtcSignal sent: {rtcSignal.Type} -> {rtcSignal.To}");
}
catch (Exception ex)
{
_sendRawToWebView("SendRtcSignal failed: " + ex.Message);
}
}
public async Task<string> GetRtcParticipants()
{
var channelId = _getCurrentChannelId();
if (string.IsNullOrWhiteSpace(channelId))
return "[]";
var participants = await ServerAPI.GetRtcParticipantsAsync(channelId);
return JsonSerializer.Serialize(participants ?? []);
}
public async Task HandleIncomingRtcSignalAsync(SocketRtcSignalMessage payload)
{
var currentChannelId = _getCurrentChannelId();
if (payload.ChannelId != currentChannelId)
return;
if (payload.SenderUsername == _username)
return;
string decryptedJson;
try
{
var privateKey = KeyStorage.LoadPrivateKey(_username);
decryptedJson = E2EeHelper.DecryptForRecipient(
new EncryptedPayload
{
CipherText = payload.CipherText,
Nonce = payload.Nonce,
Tag = payload.Tag,
EncryptedKey = payload.EncryptedKey
},
privateKey
);
}
catch (Exception ex)
{
_sendRawToWebView("RTC decrypt failed: " + ex.Message);
return;
}
RtcSignalMessage? rtcSignal;
try
{
rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(decryptedJson);
}
catch (Exception ex)
{
_sendRawToWebView("RTC signal parse failed: " + ex.Message);
return;
}
if (rtcSignal is null)
return;
if (!string.IsNullOrWhiteSpace(rtcSignal.To) &&
!string.Equals(rtcSignal.To, _username, StringComparison.OrdinalIgnoreCase))
{
_sendRawToWebView($"Ignoring RTC signal meant for {rtcSignal.To}");
return;
}
_sendRawToWebView("Received encrypted RTC signal: " + decryptedJson);
await SendRtcSignalToJsAsync(decryptedJson);
}
public Task PushRtcContextToJsAsync()
{
MainThread.BeginInvokeOnMainThread(async () =>
{
var usernameJson = JsonSerializer.Serialize(_username);
var channelIdJson = JsonSerializer.Serialize(_getCurrentChannelId());
await _hybridWebView.EvaluateJavaScriptAsync($"window.setUsername({usernameJson})");
await _hybridWebView.EvaluateJavaScriptAsync($"window.setChannelId({channelIdJson})");
});
return Task.CompletedTask;
}
private Task SendRtcSignalToJsAsync(string rawJson)
{
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)
{
_sendRawToWebView("SendRtcSignalToJsAsync failed: " + ex.Message);
}
});
return Task.CompletedTask;
}
}