AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
This commit is contained in:
@@ -36,20 +36,10 @@ async function ensurePeerConnection() {
|
|||||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
||||||
});
|
});
|
||||||
|
|
||||||
peerConnection.onicecandidate = async (event) => {
|
peerConnection.onicecandidate = (event) => {
|
||||||
if (!event.candidate || !currentChannelId || !currentUsername) return;
|
if (event.candidate) {
|
||||||
|
LogMessage("ICE candidate gathered");
|
||||||
const payload = {
|
}
|
||||||
type: "rtc_ice_candidate",
|
|
||||||
from: currentUsername,
|
|
||||||
channelId: currentChannelId,
|
|
||||||
candidate: event.candidate.candidate,
|
|
||||||
sdpMid: event.candidate.sdpMid,
|
|
||||||
sdpMLineIndex: event.candidate.sdpMLineIndex
|
|
||||||
};
|
|
||||||
|
|
||||||
LogMessage("Sending ICE candidate");
|
|
||||||
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
peerConnection.ontrack = (event) => {
|
peerConnection.ontrack = (event) => {
|
||||||
@@ -180,17 +170,13 @@ async function joinChannelCall() {
|
|||||||
|
|
||||||
LogMessage(`Joining call with media: audio=${hasAudioTrack()} video=${hasVideoTrack()}`);
|
LogMessage(`Joining call with media: audio=${hasAudioTrack()} video=${hasVideoTrack()}`);
|
||||||
|
|
||||||
const offer = await peerConnection.createOffer();
|
|
||||||
await peerConnection.setLocalDescription(offer);
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
type: "rtc_offer",
|
type: "rtc_join",
|
||||||
from: currentUsername,
|
from: currentUsername,
|
||||||
channelId: currentChannelId,
|
channelId: currentChannelId
|
||||||
sdp: offer.sdp
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LogMessage("Sending offer to channel " + currentChannelId);
|
LogMessage("Requesting join for channel " + currentChannelId);
|
||||||
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
LogMessage("joinChannelCall failed: " + err);
|
LogMessage("joinChannelCall failed: " + err);
|
||||||
@@ -254,6 +240,30 @@ async function handleRtcSignal(rawJson) {
|
|||||||
|
|
||||||
await ensurePeerConnection();
|
await ensurePeerConnection();
|
||||||
|
|
||||||
|
if (msg.type === "rtc_join_state") {
|
||||||
|
if (msg.isInitiator) {
|
||||||
|
LogMessage("No active call found. Becoming initiator.");
|
||||||
|
|
||||||
|
const offer = await peerConnection.createOffer();
|
||||||
|
await peerConnection.setLocalDescription(offer);
|
||||||
|
await waitForIceGatheringComplete(peerConnection);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
type: "rtc_offer",
|
||||||
|
from: currentUsername,
|
||||||
|
channelId: currentChannelId,
|
||||||
|
sdp: peerConnection.localDescription.sdp
|
||||||
|
};
|
||||||
|
|
||||||
|
LogMessage("Sending offer to channel " + currentChannelId);
|
||||||
|
await window.HybridWebView.InvokeDotNet("SendRtcSignal", [JSON.stringify(payload)]);
|
||||||
|
} else {
|
||||||
|
LogMessage("Active call exists. Waiting for stored offer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.type === "rtc_offer") {
|
if (msg.type === "rtc_offer") {
|
||||||
LogMessage("Incoming channel call offer from " + msg.from);
|
LogMessage("Incoming channel call offer from " + msg.from);
|
||||||
await ensureLocalMedia();
|
await ensureLocalMedia();
|
||||||
@@ -268,12 +278,13 @@ async function handleRtcSignal(rawJson) {
|
|||||||
|
|
||||||
const answer = await peerConnection.createAnswer();
|
const answer = await peerConnection.createAnswer();
|
||||||
await peerConnection.setLocalDescription(answer);
|
await peerConnection.setLocalDescription(answer);
|
||||||
|
await waitForIceGatheringComplete(peerConnection);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
type: "rtc_answer",
|
type: "rtc_answer",
|
||||||
from: currentUsername,
|
from: currentUsername,
|
||||||
channelId: msg.channelId,
|
channelId: msg.channelId,
|
||||||
sdp: answer.sdp
|
sdp: peerConnection.localDescription.sdp
|
||||||
};
|
};
|
||||||
|
|
||||||
LogMessage("Sending answer to channel " + msg.channelId);
|
LogMessage("Sending answer to channel " + msg.channelId);
|
||||||
@@ -360,6 +371,21 @@ async function loadDevices() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForIceGatheringComplete(pc) {
|
||||||
|
if (pc.iceGatheringState === "complete") return;
|
||||||
|
|
||||||
|
await new Promise(resolve => {
|
||||||
|
function checkState() {
|
||||||
|
if (pc.iceGatheringState === "complete") {
|
||||||
|
pc.removeEventListener("icegatheringstatechange", checkState);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.addEventListener("icegatheringstatechange", checkState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.handleRtcSignal = handleRtcSignal;
|
window.handleRtcSignal = handleRtcSignal;
|
||||||
|
|
||||||
window.addEventListener("HybridWebViewMessageReceived", function (e) {
|
window.addEventListener("HybridWebViewMessageReceived", function (e) {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public class ChatTest : WebSocketBehavior
|
|||||||
public static string? ServerPrivateKey { get; set; }
|
public static string? ServerPrivateKey { get; set; }
|
||||||
public static string? ChannelDbKey { get; set; }
|
public static string? ChannelDbKey { get; set; }
|
||||||
public static SurrealDb.Net.SurrealDbClient? Db { get; set; }
|
public static SurrealDb.Net.SurrealDbClient? Db { get; set; }
|
||||||
|
private static readonly Dictionary<string, string> ActiveRtcOffersByChannel = new();
|
||||||
|
private static readonly HashSet<string> ActiveRtcChannels = new();
|
||||||
|
|
||||||
protected override void OnMessage(MessageEventArgs e)
|
protected override void OnMessage(MessageEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -89,10 +91,8 @@ public class ChatTest : WebSocketBehavior
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () => { await ClientKeyService.RegisterOrUpdateKeyAsync(username, publicKey); }).GetAwaiter()
|
||||||
{
|
.GetResult();
|
||||||
await ClientKeyService.RegisterOrUpdateKeyAsync(username, publicKey);
|
|
||||||
}).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
Send($"SERVER:REGISTERED_KEY:{username}");
|
Send($"SERVER:REGISTERED_KEY:{username}");
|
||||||
}
|
}
|
||||||
@@ -384,10 +384,91 @@ public class ChatTest : WebSocketBehavior
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RtcSignalMessage? rtcSignal;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rtcSignal = JsonSerializer.Deserialize<RtcSignalMessage>(plainJson);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to parse decrypted RTC signal JSON: {ex.Message}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rtcSignal is null)
|
||||||
|
return;
|
||||||
|
|
||||||
var allKeys = Task.Run(async () => await ClientKeyService.GetAllAsync())
|
var allKeys = Task.Run(async () => await ClientKeyService.GetAllAsync())
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
|
|
||||||
|
if (rtcSignal.Type == "rtc_join")
|
||||||
|
{
|
||||||
|
var joinState = new
|
||||||
|
{
|
||||||
|
type = "rtc_join_state",
|
||||||
|
from = "server",
|
||||||
|
channelId = rtcSignal.ChannelId,
|
||||||
|
isInitiator = !ActiveRtcOffersByChannel.ContainsKey(rtcSignal.ChannelId)
|
||||||
|
};
|
||||||
|
|
||||||
|
var senderClient = allKeys.FirstOrDefault(x => x.Username == clientPayload.SenderUsername);
|
||||||
|
if (senderClient is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"No client key found for RTC join sender {clientPayload.SenderUsername}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var joinStateJson = JsonSerializer.Serialize(joinState);
|
||||||
|
var encryptedJoinState = E2EeHelper.EncryptForRecipient(joinStateJson, senderClient.PublicKey);
|
||||||
|
|
||||||
|
var joinStateOutbound = new SocketRtcSignalMessage
|
||||||
|
{
|
||||||
|
Type = "encrypted_rtc_signal",
|
||||||
|
SenderUsername = "server",
|
||||||
|
ChannelId = clientPayload.ChannelId,
|
||||||
|
CipherText = encryptedJoinState.CipherText,
|
||||||
|
Nonce = encryptedJoinState.Nonce,
|
||||||
|
Tag = encryptedJoinState.Tag,
|
||||||
|
EncryptedKey = encryptedJoinState.EncryptedKey
|
||||||
|
};
|
||||||
|
|
||||||
|
Send(JsonSerializer.Serialize(joinStateOutbound));
|
||||||
|
|
||||||
|
if (ActiveRtcOffersByChannel.TryGetValue(rtcSignal.ChannelId, out var storedOfferJson))
|
||||||
|
{
|
||||||
|
var encryptedStoredOffer = E2EeHelper.EncryptForRecipient(storedOfferJson, senderClient.PublicKey);
|
||||||
|
|
||||||
|
var storedOfferOutbound = new SocketRtcSignalMessage
|
||||||
|
{
|
||||||
|
Type = "encrypted_rtc_signal",
|
||||||
|
SenderUsername = "server",
|
||||||
|
ChannelId = clientPayload.ChannelId,
|
||||||
|
CipherText = encryptedStoredOffer.CipherText,
|
||||||
|
Nonce = encryptedStoredOffer.Nonce,
|
||||||
|
Tag = encryptedStoredOffer.Tag,
|
||||||
|
EncryptedKey = encryptedStoredOffer.EncryptedKey
|
||||||
|
};
|
||||||
|
|
||||||
|
Send(JsonSerializer.Serialize(storedOfferOutbound));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rtcSignal.Type == "rtc_offer")
|
||||||
|
{
|
||||||
|
ActiveRtcOffersByChannel[rtcSignal.ChannelId] = plainJson;
|
||||||
|
ActiveRtcChannels.Add(rtcSignal.ChannelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rtcSignal.Type == "rtc_leave")
|
||||||
|
{
|
||||||
|
ActiveRtcOffersByChannel.Remove(rtcSignal.ChannelId);
|
||||||
|
ActiveRtcChannels.Remove(rtcSignal.ChannelId);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var client in allKeys)
|
foreach (var client in allKeys)
|
||||||
{
|
{
|
||||||
if (client.Username == clientPayload.SenderUsername)
|
if (client.Username == clientPayload.SenderUsername)
|
||||||
|
|||||||
Reference in New Issue
Block a user