diff --git a/RelayServer/Endpoints/RtcEndpoints.cs b/RelayServer/Endpoints/RtcEndpoints.cs
index 0d2b5ec..29dba1d 100644
--- a/RelayServer/Endpoints/RtcEndpoints.cs
+++ b/RelayServer/Endpoints/RtcEndpoints.cs
@@ -17,7 +17,7 @@ public static class RtcEndpoints
{
await rtcCallService.WriteOfferAsync(request.ChannelId, request.Username, request.SessionDescription);
- RtcNotificationService.Broadcast(new RtcNotificationMessage
+ RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
{
Type = "rtc_offer_updated",
ChannelId = request.ChannelId,
@@ -59,7 +59,7 @@ public static class RtcEndpoints
// Sdp = request.Sdp
// });
- RtcNotificationService.Broadcast(new RtcNotificationMessage
+ RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
{
Type = "rtc_answer_updated",
ChannelId = request.ChannelId
@@ -93,7 +93,7 @@ public static class RtcEndpoints
request.Direction
);
- RtcNotificationService.Broadcast(new RtcNotificationMessage
+ RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
{
Type = "rtc_candidate_added",
ChannelId = request.ChannelId,
@@ -126,7 +126,7 @@ public static class RtcEndpoints
{
await rtcCallService.LeaveCallAsync(request.ChannelId, request.Username);
- RtcNotificationService.Broadcast(new RtcNotificationMessage
+ RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
{
Type = "rtc_call_left",
ChannelId = request.ChannelId,
diff --git a/RelayServer/Services/Chat/ChatSocketBehavior.cs b/RelayServer/Services/Chat/ChatSocketBehavior.cs
index 3d9bfa5..f90afae 100644
--- a/RelayServer/Services/Chat/ChatSocketBehavior.cs
+++ b/RelayServer/Services/Chat/ChatSocketBehavior.cs
@@ -2,6 +2,7 @@
using RelayServer.Models;
using RelayServer.Services.Crypto;
using RelayServer.Services.Data;
+using RelayServer.Services.Rtc;
using WebSocketSharp;
using WebSocketSharp.Server;
@@ -53,9 +54,31 @@ public class ChatSocketBehavior : WebSocketBehavior
HandleGetHistory(msg);
return;
}
+
+ if (msg.StartsWith("RTC_JOIN_CHANNEL|"))
+ {
+ HandleRtcJoinChannel(msg);
+ return;
+ }
+
+ if (msg.StartsWith("RTC_LEAVE_CHANNEL|"))
+ {
+ HandleRtcLeaveChannel(msg);
+ return;
+ }
HandleEncryptedChatMessage(msg);
}
+
+ ///
+ ///
+ ///
+ ///
+ protected override void OnClose(CloseEventArgs e)
+ {
+ RtcChannelPresenceService.RemoveSession(ID);
+ base.OnClose(e);
+ }
///
/// Extracts a display username from a stored user record id value.
@@ -419,6 +442,10 @@ public class ChatSocketBehavior : WebSocketBehavior
.GetResult();
}
+ ///
+ ///
+ ///
+ ///
private bool EnsureCoreReady()
{
if (ClientKeyService is null || Db is null)
@@ -430,6 +457,10 @@ public class ChatSocketBehavior : WebSocketBehavior
return true;
}
+ ///
+ ///
+ ///
+ ///
private bool EnsureCryptoReady()
{
if (string.IsNullOrWhiteSpace(ServerPrivateKey) || string.IsNullOrWhiteSpace(ChannelDbKey))
@@ -446,4 +477,50 @@ public class ChatSocketBehavior : WebSocketBehavior
return true;
}
+
+ ///
+ ///
+ ///
+ ///
+ private void HandleRtcJoinChannel(string msg)
+ {
+ var parts = msg.Split('|', 3);
+ if (parts.Length < 3)
+ {
+ Console.WriteLine("Invalid RTC_JOIN_CHANNEL payload.");
+ return;
+ }
+
+ var username = parts[1];
+ var channelId = parts[2];
+
+ RtcChannelPresenceService.SetUser(ID, username);
+ RtcChannelPresenceService.JoinChannel(ID, channelId);
+
+ Console.WriteLine($"RTC presence joined: session={ID}, user={username}, channel={channelId}");
+ }
+
+ ///
+ ///
+ ///
+ ///
+ private void HandleRtcLeaveChannel(string msg)
+ {
+ var parts = msg.Split('|', 3);
+ if (parts.Length < 3)
+ {
+ Console.WriteLine("Invalid RTC_LEAVE_CHANNEL payload.");
+ return;
+ }
+
+ var username = parts[1];
+ var channelId = parts[2];
+
+ if (RtcChannelPresenceService.IsInChannel(ID, channelId))
+ {
+ RtcChannelPresenceService.LeaveChannel(ID);
+ }
+
+ Console.WriteLine($"RTC presence left: session={ID}, user={username}, channel={channelId}");
+ }
}
\ No newline at end of file
diff --git a/RelayServer/Services/Rtc/RtcChannelPresenceService.cs b/RelayServer/Services/Rtc/RtcChannelPresenceService.cs
new file mode 100644
index 0000000..581f5b7
--- /dev/null
+++ b/RelayServer/Services/Rtc/RtcChannelPresenceService.cs
@@ -0,0 +1,55 @@
+using System.Collections.Concurrent;
+
+namespace RelayServer.Services.Rtc;
+
+public static class RtcChannelPresenceService
+{
+ private static readonly ConcurrentDictionary SessionToChannel = new();
+ private static readonly ConcurrentDictionary SessionToUsername = new();
+
+ public static void SetUser(string sessionId, string username)
+ {
+ SessionToUsername[sessionId] = username;
+ }
+
+ public static void JoinChannel(string sessionId, string channelId)
+ {
+ SessionToChannel[sessionId] = channelId;
+ }
+
+ public static void LeaveChannel(string sessionId)
+ {
+ SessionToChannel.TryRemove(sessionId, out _);
+ }
+
+ public static void RemoveSession(string sessionId)
+ {
+ SessionToChannel.TryRemove(sessionId, out _);
+ SessionToUsername.TryRemove(sessionId, out _);
+ }
+
+ public static IReadOnlyList GetSessionsInChannel(string channelId)
+ {
+ return SessionToChannel
+ .Where(x => x.Value == channelId)
+ .Select(x => x.Key)
+ .ToList();
+ }
+
+ public static IReadOnlyList GetUsersInChannel(string channelId)
+ {
+ var sessionIds = GetSessionsInChannel(channelId);
+
+ return sessionIds
+ .Where(id => SessionToUsername.ContainsKey(id))
+ .Select(id => SessionToUsername[id])
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+ }
+
+ public static bool IsInChannel(string sessionId, string channelId)
+ {
+ return SessionToChannel.TryGetValue(sessionId, out var currentChannel) &&
+ string.Equals(currentChannel, channelId, StringComparison.Ordinal);
+ }
+}
\ No newline at end of file
diff --git a/RelayServer/Services/Rtc/RtcNotificationService.cs b/RelayServer/Services/Rtc/RtcNotificationService.cs
index 5c4f209..9968d74 100644
--- a/RelayServer/Services/Rtc/RtcNotificationService.cs
+++ b/RelayServer/Services/Rtc/RtcNotificationService.cs
@@ -8,12 +8,21 @@ public static class RtcNotificationService
{
public static WebSocketServer? Server { get; set; }
- public static void Broadcast(RtcNotificationMessage message)
+ public static void BroadcastToChannel(RtcNotificationMessage message)
{
if (Server is null)
return;
+ var host = Server.WebSocketServices["/"];
+ if (host is null)
+ return;
+
var json = JsonSerializer.Serialize(message);
- Server.WebSocketServices["/"]?.Sessions.Broadcast(json);
+ var sessionIds = RtcChannelPresenceService.GetSessionsInChannel(message.ChannelId);
+
+ foreach (var sessionId in sessionIds)
+ {
+ host.Sessions.SendTo(json, sessionId);
+ }
}
}
\ No newline at end of file