255 lines
8.2 KiB
C#
255 lines
8.2 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
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.SendRtcJoinChannel(channelId);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public void LeaveRtcChannel()
|
|
{
|
|
var channelId = _getCurrentChannelId();
|
|
|
|
if (string.IsNullOrWhiteSpace(channelId))
|
|
return;
|
|
|
|
_socket.SendRtcLeaveChannel(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;
|
|
|
|
// _sendRawToWebView($"RTC_SIGNAL file: {JsonSerializer.Serialize(rtcSignal)}");
|
|
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)
|
|
{
|
|
// _sendRawToWebView("HandleIncomingRtcSignal called");
|
|
var currentChannelId = _getCurrentChannelId();
|
|
|
|
if (payload.ChannelId != currentChannelId)
|
|
{
|
|
_sendRawToWebView("Channel id does not match");
|
|
return;
|
|
}
|
|
|
|
if (payload.SenderUsername == _username)
|
|
{
|
|
_sendRawToWebView("Received own message");
|
|
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);
|
|
// _sendRawToWebView($"Received Encrypted Signal: [{rtcSignal.From}]: {rtcSignal.Offer}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_sendRawToWebView("RTC signal parse failed: " + ex.Message);
|
|
return;
|
|
}
|
|
|
|
if (rtcSignal is null)
|
|
{
|
|
_sendRawToWebView("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(rtcSignal);
|
|
}
|
|
|
|
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(RtcSignalMessage data)
|
|
{
|
|
if (data.Type == "rtc_offer" || data.Type == "rtc_answer")
|
|
{
|
|
data.Sdp = data.Sdp.Replace("\r\n", "(rn)");
|
|
}
|
|
MainThread.BeginInvokeOnMainThread(async () =>
|
|
{
|
|
try
|
|
{
|
|
// await _hybridWebView.InvokeJavaScriptAsync("testIndex", [JsonSerializer.Serialize(data)], [RtcJsType.Default.String]);
|
|
await _hybridWebView.InvokeJavaScriptAsync("testIndex", [data], [RtcJsType.Default.RtcSignalMessage]);
|
|
#region OldDebugger
|
|
// var jsArg = JsonSerializer.Serialize(data);
|
|
//
|
|
// 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);
|
|
// }}
|
|
// ");
|
|
#endregion
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_sendRawToWebView("SendRtcSignalToJsAsync failed: " + ex.Message);
|
|
}
|
|
});
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
|
|
[JsonSourceGenerationOptions(WriteIndented = false)]
|
|
[JsonSerializable(typeof(RtcDescription))]
|
|
[JsonSerializable(typeof(List<RtcSignalMessage>))]
|
|
[JsonSerializable(typeof(RtcSignalMessage))]
|
|
[JsonSerializable(typeof(IceCandidate))]
|
|
[JsonSerializable(typeof(List<IceCandidate>))]
|
|
[JsonSerializable(typeof(string))]
|
|
internal partial class RtcJsType : JsonSerializerContext
|
|
{
|
|
} |