228 lines
6.8 KiB
C#
228 lines
6.8 KiB
C#
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;
|
|
}
|
|
} |