From a4f0175ca48b1b56924e022ac7b7bb81095c5d6e Mon Sep 17 00:00:00 2001 From: RuKira Date: Thu, 2 Apr 2026 13:36:54 -0400 Subject: [PATCH] Some orgnization, and cleanup to come. --- RelayServer/Endpoints/RtcEndpoints.cs | 63 ++++++++ .../Models/{ => Chat}/ChannelMessages.cs | 0 RelayServer/Models/{ => Chat}/Channels.cs | 0 .../Models/{ => Chat}/SocketChannelInfo.cs | 0 .../Models/{ => Chat}/SocketChannelList.cs | 0 .../{ => Chat}/SocketEncryptedMessage.cs | 0 .../Models/{ => Crypto}/ClientPublicKeys.cs | 0 .../Models/{ => Crypto}/RtcSignalMessage.cs | 0 .../{ => Crypto}/ServerEncryptionKeys.cs | 0 .../{ => Crypto}/ServerPublicKeyMessage.cs | 0 .../{ => Crypto}/SocketRtcSignalMessage.cs | 0 RelayServer/Models/Rtc/RtcActiveCall.cs | 12 ++ RelayServer/Models/Rtc/RtcAnswer.cs | 12 ++ RelayServer/Models/Rtc/RtcIceCandidate.cs | 14 ++ RelayServer/Models/Rtc/RtcJoinRequest.cs | 7 + RelayServer/Models/Rtc/RtcJoinResponse.cs | 10 ++ RelayServer/Models/Rtc/RtcLeaveRequest.cs | 7 + RelayServer/Models/Rtc/RtcOffer.cs | 12 ++ .../Models/{ => Server}/ServerMembers.cs | 0 RelayServer/Models/{ => Server}/Servers.cs | 0 RelayServer/Program.cs | 1 - .../{ => Chat}/ChannelCryptoService.cs | 0 .../{ => Chat}/ChannelMessageService.cs | 0 RelayServer/Services/{ => Chat}/ChatTest.cs | 0 RelayServer/Services/ClientKeyService.cs | 61 -------- .../Services/{ => Core}/CoreClientService.cs | 0 .../{ => Core}/ServerBootstrapService.cs | 0 .../Services/{ => Crypto}/E2EeHelper.cs | 0 RelayServer/Services/Rtc/RtcCallService.cs | 148 ++++++++++++++++++ RelayServer/Services/SurrealService.cs | 21 --- 30 files changed, 285 insertions(+), 83 deletions(-) create mode 100644 RelayServer/Endpoints/RtcEndpoints.cs rename RelayServer/Models/{ => Chat}/ChannelMessages.cs (100%) rename RelayServer/Models/{ => Chat}/Channels.cs (100%) rename RelayServer/Models/{ => Chat}/SocketChannelInfo.cs (100%) rename RelayServer/Models/{ => Chat}/SocketChannelList.cs (100%) rename RelayServer/Models/{ => Chat}/SocketEncryptedMessage.cs (100%) rename RelayServer/Models/{ => Crypto}/ClientPublicKeys.cs (100%) rename RelayServer/Models/{ => Crypto}/RtcSignalMessage.cs (100%) rename RelayServer/Models/{ => Crypto}/ServerEncryptionKeys.cs (100%) rename RelayServer/Models/{ => Crypto}/ServerPublicKeyMessage.cs (100%) rename RelayServer/Models/{ => Crypto}/SocketRtcSignalMessage.cs (100%) create mode 100644 RelayServer/Models/Rtc/RtcActiveCall.cs create mode 100644 RelayServer/Models/Rtc/RtcAnswer.cs create mode 100644 RelayServer/Models/Rtc/RtcIceCandidate.cs create mode 100644 RelayServer/Models/Rtc/RtcJoinRequest.cs create mode 100644 RelayServer/Models/Rtc/RtcJoinResponse.cs create mode 100644 RelayServer/Models/Rtc/RtcLeaveRequest.cs create mode 100644 RelayServer/Models/Rtc/RtcOffer.cs rename RelayServer/Models/{ => Server}/ServerMembers.cs (100%) rename RelayServer/Models/{ => Server}/Servers.cs (100%) rename RelayServer/Services/{ => Chat}/ChannelCryptoService.cs (100%) rename RelayServer/Services/{ => Chat}/ChannelMessageService.cs (100%) rename RelayServer/Services/{ => Chat}/ChatTest.cs (100%) delete mode 100644 RelayServer/Services/ClientKeyService.cs rename RelayServer/Services/{ => Core}/CoreClientService.cs (100%) rename RelayServer/Services/{ => Core}/ServerBootstrapService.cs (100%) rename RelayServer/Services/{ => Crypto}/E2EeHelper.cs (100%) create mode 100644 RelayServer/Services/Rtc/RtcCallService.cs delete mode 100644 RelayServer/Services/SurrealService.cs diff --git a/RelayServer/Endpoints/RtcEndpoints.cs b/RelayServer/Endpoints/RtcEndpoints.cs new file mode 100644 index 0000000..8990147 --- /dev/null +++ b/RelayServer/Endpoints/RtcEndpoints.cs @@ -0,0 +1,63 @@ +using RelayServer.Models.Rtc; +using RelayServer.Services.Rtc; + +namespace RelayServer.Endpoints; + +public static class RtcEndpoints +{ + public static void MapRtcEndpoints(this WebApplication app) + { + app.MapPost("/api/rtc/join", async (RtcJoinRequest request, RtcCallService rtcCallService) => + { + return Results.Ok(await rtcCallService.JoinCallAsync(request.ChannelId, request.Username)); + }); + + app.MapPost("/api/rtc/offer", async (RtcOffer request, RtcCallService rtcCallService) => + { + await rtcCallService.WriteOfferAsync(request.ChannelId, request.Username, request.Sdp); + return Results.Ok(); + }); + + app.MapGet("/api/rtc/offer/{channelId}", async (string channelId, RtcCallService rtcCallService) => + { + var offer = await rtcCallService.GetOfferAsync(channelId); + return offer is null ? Results.NotFound() : Results.Ok(offer); + }); + + app.MapPost("/api/rtc/answer", async (RtcAnswer request, RtcCallService rtcCallService) => + { + await rtcCallService.WriteAnswerAsync(request.ChannelId, request.OfferUser, request.AnswerUser, request.Sdp); + return Results.Ok(); + }); + + app.MapGet("/api/rtc/answers/{channelId}", async (string channelId, RtcCallService rtcCallService) => + { + return Results.Ok(await rtcCallService.GetAnswersAsync(channelId)); + }); + + app.MapPost("/api/rtc/candidate", async (RtcIceCandidate request, RtcCallService rtcCallService) => + { + await rtcCallService.WriteIceCandidateAsync( + request.ChannelId, + request.Username, + request.Candidate, + request.SdpMid, + request.SdpMLineIndex, + request.Direction + ); + + return Results.Ok(); + }); + + app.MapGet("/api/rtc/candidates/{channelId}", async (string channelId, RtcCallService rtcCallService) => + { + return Results.Ok(await rtcCallService.GetIceCandidatesAsync(channelId)); + }); + + app.MapPost("/api/rtc/leave", async (RtcLeaveRequest request, RtcCallService rtcCallService) => + { + await rtcCallService.LeaveCallAsync(request.ChannelId, request.Username); + return Results.Ok(); + }); + } +} \ No newline at end of file diff --git a/RelayServer/Models/ChannelMessages.cs b/RelayServer/Models/Chat/ChannelMessages.cs similarity index 100% rename from RelayServer/Models/ChannelMessages.cs rename to RelayServer/Models/Chat/ChannelMessages.cs diff --git a/RelayServer/Models/Channels.cs b/RelayServer/Models/Chat/Channels.cs similarity index 100% rename from RelayServer/Models/Channels.cs rename to RelayServer/Models/Chat/Channels.cs diff --git a/RelayServer/Models/SocketChannelInfo.cs b/RelayServer/Models/Chat/SocketChannelInfo.cs similarity index 100% rename from RelayServer/Models/SocketChannelInfo.cs rename to RelayServer/Models/Chat/SocketChannelInfo.cs diff --git a/RelayServer/Models/SocketChannelList.cs b/RelayServer/Models/Chat/SocketChannelList.cs similarity index 100% rename from RelayServer/Models/SocketChannelList.cs rename to RelayServer/Models/Chat/SocketChannelList.cs diff --git a/RelayServer/Models/SocketEncryptedMessage.cs b/RelayServer/Models/Chat/SocketEncryptedMessage.cs similarity index 100% rename from RelayServer/Models/SocketEncryptedMessage.cs rename to RelayServer/Models/Chat/SocketEncryptedMessage.cs diff --git a/RelayServer/Models/ClientPublicKeys.cs b/RelayServer/Models/Crypto/ClientPublicKeys.cs similarity index 100% rename from RelayServer/Models/ClientPublicKeys.cs rename to RelayServer/Models/Crypto/ClientPublicKeys.cs diff --git a/RelayServer/Models/RtcSignalMessage.cs b/RelayServer/Models/Crypto/RtcSignalMessage.cs similarity index 100% rename from RelayServer/Models/RtcSignalMessage.cs rename to RelayServer/Models/Crypto/RtcSignalMessage.cs diff --git a/RelayServer/Models/ServerEncryptionKeys.cs b/RelayServer/Models/Crypto/ServerEncryptionKeys.cs similarity index 100% rename from RelayServer/Models/ServerEncryptionKeys.cs rename to RelayServer/Models/Crypto/ServerEncryptionKeys.cs diff --git a/RelayServer/Models/ServerPublicKeyMessage.cs b/RelayServer/Models/Crypto/ServerPublicKeyMessage.cs similarity index 100% rename from RelayServer/Models/ServerPublicKeyMessage.cs rename to RelayServer/Models/Crypto/ServerPublicKeyMessage.cs diff --git a/RelayServer/Models/SocketRtcSignalMessage.cs b/RelayServer/Models/Crypto/SocketRtcSignalMessage.cs similarity index 100% rename from RelayServer/Models/SocketRtcSignalMessage.cs rename to RelayServer/Models/Crypto/SocketRtcSignalMessage.cs diff --git a/RelayServer/Models/Rtc/RtcActiveCall.cs b/RelayServer/Models/Rtc/RtcActiveCall.cs new file mode 100644 index 0000000..f299cf8 --- /dev/null +++ b/RelayServer/Models/Rtc/RtcActiveCall.cs @@ -0,0 +1,12 @@ +using SurrealDb.Net.Models; + +namespace RelayServer.Models.Rtc; + +public class RtcActiveCall : Record +{ + public required string ChannelId { get; set; } + public required string OfferUser { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/RelayServer/Models/Rtc/RtcAnswer.cs b/RelayServer/Models/Rtc/RtcAnswer.cs new file mode 100644 index 0000000..2278152 --- /dev/null +++ b/RelayServer/Models/Rtc/RtcAnswer.cs @@ -0,0 +1,12 @@ +using SurrealDb.Net.Models; + +namespace RelayServer.Models.Rtc; + +public class RtcAnswer : Record +{ + public required string ChannelId { get; set; } + public required string OfferUser { get; set; } + public required string AnswerUser { get; set; } + public required string Sdp { get; set; } + public DateTime CreatedAt { get; set; } +} \ No newline at end of file diff --git a/RelayServer/Models/Rtc/RtcIceCandidate.cs b/RelayServer/Models/Rtc/RtcIceCandidate.cs new file mode 100644 index 0000000..811376e --- /dev/null +++ b/RelayServer/Models/Rtc/RtcIceCandidate.cs @@ -0,0 +1,14 @@ +using SurrealDb.Net.Models; + +namespace RelayServer.Models.Rtc; + +public class RtcIceCandidate : Record +{ + public required string ChannelId { get; set; } + public required string Username { get; set; } + public required string Candidate { get; set; } + public string? SdpMid { get; set; } + public int? SdpMLineIndex { get; set; } + public required string Direction { get; set; } // "offer" or "answer" + public DateTime CreatedAt { get; set; } +} \ No newline at end of file diff --git a/RelayServer/Models/Rtc/RtcJoinRequest.cs b/RelayServer/Models/Rtc/RtcJoinRequest.cs new file mode 100644 index 0000000..c126a12 --- /dev/null +++ b/RelayServer/Models/Rtc/RtcJoinRequest.cs @@ -0,0 +1,7 @@ +namespace RelayServer.Models.Rtc; + +public class RtcJoinRequest +{ + public required string ChannelId { get; set; } + public required string Username { get; set; } +} \ No newline at end of file diff --git a/RelayServer/Models/Rtc/RtcJoinResponse.cs b/RelayServer/Models/Rtc/RtcJoinResponse.cs new file mode 100644 index 0000000..da09179 --- /dev/null +++ b/RelayServer/Models/Rtc/RtcJoinResponse.cs @@ -0,0 +1,10 @@ +namespace RelayServer.Models.Rtc; + +public class RtcJoinResponse +{ + public required string ChannelId { get; set; } + public bool HasActiveCall { get; set; } + public bool IsOfferer { get; set; } + public string? OfferUser { get; set; } + public string? OfferSdp { get; set; } +} \ No newline at end of file diff --git a/RelayServer/Models/Rtc/RtcLeaveRequest.cs b/RelayServer/Models/Rtc/RtcLeaveRequest.cs new file mode 100644 index 0000000..8b41fea --- /dev/null +++ b/RelayServer/Models/Rtc/RtcLeaveRequest.cs @@ -0,0 +1,7 @@ +namespace RelayServer.Models.Rtc; + +public class RtcLeaveRequest +{ + public required string ChannelId { get; set; } + public required string Username { get; set; } +} \ No newline at end of file diff --git a/RelayServer/Models/Rtc/RtcOffer.cs b/RelayServer/Models/Rtc/RtcOffer.cs new file mode 100644 index 0000000..13f5c51 --- /dev/null +++ b/RelayServer/Models/Rtc/RtcOffer.cs @@ -0,0 +1,12 @@ +using SurrealDb.Net.Models; + +namespace RelayServer.Models.Rtc; + +public class RtcOffer : Record +{ + public required string ChannelId { get; set; } + public required string Username { get; set; } + public required string Sdp { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/RelayServer/Models/ServerMembers.cs b/RelayServer/Models/Server/ServerMembers.cs similarity index 100% rename from RelayServer/Models/ServerMembers.cs rename to RelayServer/Models/Server/ServerMembers.cs diff --git a/RelayServer/Models/Servers.cs b/RelayServer/Models/Server/Servers.cs similarity index 100% rename from RelayServer/Models/Servers.cs rename to RelayServer/Models/Server/Servers.cs diff --git a/RelayServer/Program.cs b/RelayServer/Program.cs index e6a0a31..f9243ac 100644 --- a/RelayServer/Program.cs +++ b/RelayServer/Program.cs @@ -2,7 +2,6 @@ using System.Text.Json; using RelayServer.Services; using WebSocketSharp.Server; using Microsoft.AspNetCore.SignalR; -using Microsoft.AspNetCore.Builder; using RelayServer.Models; var surrealService = new SurrealService(); diff --git a/RelayServer/Services/ChannelCryptoService.cs b/RelayServer/Services/Chat/ChannelCryptoService.cs similarity index 100% rename from RelayServer/Services/ChannelCryptoService.cs rename to RelayServer/Services/Chat/ChannelCryptoService.cs diff --git a/RelayServer/Services/ChannelMessageService.cs b/RelayServer/Services/Chat/ChannelMessageService.cs similarity index 100% rename from RelayServer/Services/ChannelMessageService.cs rename to RelayServer/Services/Chat/ChannelMessageService.cs diff --git a/RelayServer/Services/ChatTest.cs b/RelayServer/Services/Chat/ChatTest.cs similarity index 100% rename from RelayServer/Services/ChatTest.cs rename to RelayServer/Services/Chat/ChatTest.cs diff --git a/RelayServer/Services/ClientKeyService.cs b/RelayServer/Services/ClientKeyService.cs deleted file mode 100644 index 8f2ab2f..0000000 --- a/RelayServer/Services/ClientKeyService.cs +++ /dev/null @@ -1,61 +0,0 @@ -using RelayServer.Models; -using SurrealDb.Net; - -namespace RelayServer.Services; - -public sealed class ClientKeyService -{ - private readonly SurrealDbClient _db; - - public ClientKeyService(SurrealDbClient db) - { - _db = db; - } - - public async Task RegisterOrUpdateKeyAsync(string username, string publicKey) - { - var allKeys = await _db.Select("client_public_keys"); - - var existing = allKeys.FirstOrDefault(x => x.Username == username); - - if (existing is null) - { - await _db.Create("client_public_keys", new ClientPublicKeys - { - Username = username, - PublicKey = publicKey, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow - }); - - Console.WriteLine($"Stored public key for {username}"); - return; - } - - existing.PublicKey = publicKey; - existing.UpdatedAt = DateTime.UtcNow; - - await _db.Merge(new ClientPublicKeys - { - Id = existing.Id, - Username = existing.Username, - PublicKey = existing.PublicKey, - CreatedAt = existing.CreatedAt, - UpdatedAt = existing.UpdatedAt - }); - - Console.WriteLine($"Updated public key for {username}"); - } - - public async Task GetByUsernameAsync(string username) - { - var allKeys = await _db.Select("client_public_keys"); - return allKeys.FirstOrDefault(x => x.Username == username); - } - - public async Task> GetAllAsync() - { - var allKeys = await _db.Select("client_public_keys"); - return allKeys.ToList(); - } -} \ No newline at end of file diff --git a/RelayServer/Services/CoreClientService.cs b/RelayServer/Services/Core/CoreClientService.cs similarity index 100% rename from RelayServer/Services/CoreClientService.cs rename to RelayServer/Services/Core/CoreClientService.cs diff --git a/RelayServer/Services/ServerBootstrapService.cs b/RelayServer/Services/Core/ServerBootstrapService.cs similarity index 100% rename from RelayServer/Services/ServerBootstrapService.cs rename to RelayServer/Services/Core/ServerBootstrapService.cs diff --git a/RelayServer/Services/E2EeHelper.cs b/RelayServer/Services/Crypto/E2EeHelper.cs similarity index 100% rename from RelayServer/Services/E2EeHelper.cs rename to RelayServer/Services/Crypto/E2EeHelper.cs diff --git a/RelayServer/Services/Rtc/RtcCallService.cs b/RelayServer/Services/Rtc/RtcCallService.cs new file mode 100644 index 0000000..be10411 --- /dev/null +++ b/RelayServer/Services/Rtc/RtcCallService.cs @@ -0,0 +1,148 @@ +using RelayServer.Models.Rtc; +using SurrealDb.Net; + +namespace RelayServer.Services.Rtc; + +public sealed class RtcCallService +{ + private readonly SurrealDbClient _db; + + public RtcCallService(SurrealDbClient db) + { + _db = db; + } + + public async Task JoinCallAsync(string channelId, string username) + { + var activeCalls = await _db.Select("rtc_active_calls"); + var activeCall = activeCalls.FirstOrDefault(x => x.ChannelId == channelId && x.IsActive); + + if (activeCall is null) + { + await _db.Create("rtc_active_calls", new RtcActiveCall + { + ChannelId = channelId, + OfferUser = username, + IsActive = true, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }); + + return new RtcJoinResponse + { + ChannelId = channelId, + HasActiveCall = false, + IsOfferer = true, + OfferUser = username, + OfferSdp = null + }; + } + + var offers = await _db.Select("rtc_offers"); + var offer = offers + .Where(x => x.ChannelId == channelId) + .OrderByDescending(x => x.CreatedAt) + .FirstOrDefault(); + + return new RtcJoinResponse + { + ChannelId = channelId, + HasActiveCall = true, + IsOfferer = false, + OfferUser = activeCall.OfferUser, + OfferSdp = offer?.Sdp + }; + } + + public async Task WriteOfferAsync(string channelId, string username, string sdp) + { + var offers = await _db.Select("rtc_offers"); + var existing = offers.FirstOrDefault(x => x.ChannelId == channelId && x.Username == username); + + if (existing is null) + { + await _db.Create("rtc_offers", new RtcOffer + { + ChannelId = channelId, + Username = username, + Sdp = sdp, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }); + return; + } + + existing.Sdp = sdp; + existing.UpdatedAt = DateTime.UtcNow; + await _db.Merge(existing); + } + + public async Task GetOfferAsync(string channelId) + { + var offers = await _db.Select("rtc_offers"); + return offers + .Where(x => x.ChannelId == channelId) + .OrderByDescending(x => x.CreatedAt) + .FirstOrDefault(); + } + + public async Task WriteAnswerAsync(string channelId, string offerUser, string answerUser, string sdp) + { + await _db.Create("rtc_answers", new RtcAnswer + { + ChannelId = channelId, + OfferUser = offerUser, + AnswerUser = answerUser, + Sdp = sdp, + CreatedAt = DateTime.UtcNow + }); + } + + public async Task> GetAnswersAsync(string channelId) + { + var answers = await _db.Select("rtc_answers"); + return answers + .Where(x => x.ChannelId == channelId) + .OrderBy(x => x.CreatedAt) + .ToList(); + } + + public async Task WriteIceCandidateAsync(string channelId, string username, string candidate, string? sdpMid, int? sdpMLineIndex, string direction) + { + await _db.Create("rtc_ice_candidates", new RtcIceCandidate + { + ChannelId = channelId, + Username = username, + Candidate = candidate, + SdpMid = sdpMid, + SdpMLineIndex = sdpMLineIndex, + Direction = direction, + CreatedAt = DateTime.UtcNow + }); + } + + public async Task> GetIceCandidatesAsync(string channelId) + { + var candidates = await _db.Select("rtc_ice_candidates"); + return candidates + .Where(x => x.ChannelId == channelId) + .OrderBy(x => x.CreatedAt) + .ToList(); + } + + public async Task LeaveCallAsync(string channelId, string username) + { + var activeCalls = await _db.Select("rtc_active_calls"); + var activeCall = activeCalls.FirstOrDefault(x => x.ChannelId == channelId && x.IsActive); + + if (activeCall is null) + return; + + if (activeCall.OfferUser == username) + { + activeCall.IsActive = false; + activeCall.UpdatedAt = DateTime.UtcNow; + await _db.Merge(activeCall); + } + } +} \ No newline at end of file diff --git a/RelayServer/Services/SurrealService.cs b/RelayServer/Services/SurrealService.cs deleted file mode 100644 index 9efb2fd..0000000 --- a/RelayServer/Services/SurrealService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using SurrealDb.Net; -using SurrealDb.Net.Models.Auth; - -namespace RelayServer.Services; - -public sealed class SurrealService -{ - public async Task ConnectAsync() - { - var db = new SurrealDbClient("ws://127.0.0.1:8000/rpc"); - - await db.SignIn(new RootAuth - { - Username = "root", - Password = "secret" - }); - - await db.Use("test", "test"); - return db; - } -} \ No newline at end of file