Setup new services required for change
This commit is contained in:
127
RelayClient/Services/RelaySocketClient.cs
Normal file
127
RelayClient/Services/RelaySocketClient.cs
Normal 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
228
RelayClient/Services/RtcBridgeService.cs
Normal file
228
RelayClient/Services/RtcBridgeService.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user