voice chat [TEST]
All checks were successful
Server Build / publish (push) Successful in 32s
Voice Build / publish (push) Successful in 26s

This commit is contained in:
olcxja 2026-08-22 12:36:54 +02:00
commit 2ca092b017
6 changed files with 226 additions and 51 deletions

View file

@ -1038,6 +1038,24 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
, password));
break;
}
case "room/voice/join":
await context.Response.WriteAsync(
Encryption.Encryption.EncryptString(
await Room.Shared.VoiceJoin(id, serializedBody.string2)
, password));
break;
case "room/voice/leave":
await context.Response.WriteAsync(
Encryption.Encryption.EncryptString(
await Room.Shared.VoiceLeave(id, serializedBody.string2)
, password));
break;
case "room/signal":
await context.Response.WriteAsync(
Encryption.Encryption.EncryptString(
await Room.Shared.RoomSignal(id, serializedBody.string2)
, password));
break;
case "group/message/react/remove":
{
Universal3String msgReact = JsonSerializer.Deserialize(
@ -1166,11 +1184,20 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
return;
}
SemaphoreSlim userLock = Utils.GetUserLock(id);
await userLock.WaitAsync();
try
{
await context.Response.WriteAsync(
Encryption.Encryption.EncryptString(
await Utils.UpdateUserPublicStorageEntryRaw(id, target, fileBytes)
, password));
}
finally
{
userLock.Release();
}
}
public static async Task UploadDmAttachmentStaging(HttpContext context, Func<Task> next, IQueryCollection query)
{
if (!query.TryGetValue("id", out var idQuery)) return;

View file

@ -379,10 +379,6 @@ public class Utils
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public";
string path = $"{dirPath}/{entry}";
SemaphoreSlim userLock = GetUserLock(id);
await userLock.WaitAsync();
try
{
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
@ -392,11 +388,6 @@ public class Utils
}
await Fs.WriteFile(path, contentBytes);
}
finally
{
userLock.Release();
}
return "success:storage.entry.updated";
}
@ -418,10 +409,6 @@ public class Utils
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public";
string path = $"{dirPath}/{entry}";
SemaphoreSlim userLock = GetUserLock(id);
await userLock.WaitAsync();
try
{
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
@ -431,11 +418,6 @@ public class Utils
}
await Fs.WriteFile(path, contentBytes);
}
finally
{
userLock.Release();
}
return "success:storage.entry.updated";
}
@ -471,10 +453,6 @@ public class Utils
string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private";
string path = $"{dirPath}/{entry}";
SemaphoreSlim userLock = GetUserLock(id);
await userLock.WaitAsync();
try
{
long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath);
long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0;
@ -484,11 +462,6 @@ public class Utils
}
await Fs.WriteFile(path, contentBytes);
}
finally
{
userLock.Release();
}
return "success:storage.entry.updated";
}

View file

@ -600,4 +600,132 @@ public class Shared
if (roomType == "groups") return "group";
return "room";
}
public static async Task<string> VoiceJoin(string id, string body)
{
Universal2String serializedBody = JsonSerializer.Deserialize(
body,
AppJsonSerializerContext.Default.Universal2String
);
string roomType = serializedBody.string1;
string roomId = serializedBody.string2;
if (!await IsMemberOfRoom(id, roomType, roomId)) return "error:forbidden";
var callList = ActiveCalls.GetOrAdd(roomId, _ => new List<string>());
lock (callList)
{
if (!callList.Contains(id))
{
callList.Add(id);
}
}
string callState;
lock (callList) { callState = JsonSerializer.Serialize(callList, AppJsonSerializerContext.Default.ListString); }
string bestIp = "";
long bestLoad = long.MaxValue;
foreach (var ip in VOICE_IPS)
{
try
{
using var client = new System.Net.Http.HttpClient();
client.Timeout = TimeSpan.FromSeconds(2);
string url = ip.StartsWith("http") ? ip : $"http://{ip}";
string loadStr = await client.GetStringAsync($"{url}/load");
if (long.TryParse(loadStr, out long load))
{
if (load < bestLoad)
{
bestLoad = load;
bestIp = ip;
}
}
}
catch {}
}
if (string.IsNullOrEmpty(bestIp))
{
if (VOICE_IPS.Count > 0) bestIp = VOICE_IPS.First();
else return "error:no.voice.servers";
}
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
foreach(string memberWD in members)
{
if (Account.Utils.IsUserLocal(memberWD, out string _))
{
string memberId = Account.Utils.GetValidIdOrZero(memberWD);
_ = Utils.Utils.SendWsMessage(memberId, $"call_update:{roomId}:{callState}");
}
}
return $"success:{bestIp}";
}
public static async Task<string> VoiceLeave(string id, string body)
{
Universal2String serializedBody = JsonSerializer.Deserialize(
body,
AppJsonSerializerContext.Default.Universal2String
);
string roomType = serializedBody.string1;
string roomId = serializedBody.string2;
if (ActiveCalls.TryGetValue(roomId, out var callList))
{
bool removed = false;
lock (callList)
{
if (callList.Contains(id))
{
callList.Remove(id);
removed = true;
}
}
if (removed)
{
string callState;
lock (callList) { callState = JsonSerializer.Serialize(callList, AppJsonSerializerContext.Default.ListString); }
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
foreach(string memberWD in members)
{
if (Account.Utils.IsUserLocal(memberWD, out string _))
{
string memberId = Account.Utils.GetValidIdOrZero(memberWD);
_ = Utils.Utils.SendWsMessage(memberId, $"call_update:{roomId}:{callState}");
}
}
}
}
return "success";
}
public static async Task<string> RoomSignal(string id, string body)
{
Universal3String serializedBody = JsonSerializer.Deserialize(
body,
AppJsonSerializerContext.Default.Universal3String
);
string roomType = serializedBody.string1;
string roomId = serializedBody.string2;
string signalPayload = serializedBody.string3;
if (!await IsMemberOfRoom(id, roomType, roomId)) return "error:forbidden";
string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members");
foreach(string memberWD in members)
{
if (Account.Utils.IsUserLocal(memberWD, out string _))
{
string memberId = Account.Utils.GetValidIdOrZero(memberWD);
_ = Utils.Utils.SendWsMessage(memberId, $"signal:{roomId}:{signalPayload}");
}
}
return "success";
}
}

View file

@ -18,6 +18,7 @@ using System.Collections.Generic;
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(GroupKeyRotationPayload))]
[JsonSerializable(typeof(string[]))]
[JsonSerializable(typeof(List<string>))]
public partial class AppJsonSerializerContext : JsonSerializerContext
{
}

View file

@ -105,6 +105,7 @@ public class Utils
};
public static ConcurrentDictionary<string, List<System.Net.WebSockets.WebSocket>> ActiveWebSockets = new();
public static ConcurrentDictionary<string, List<string>> ActiveCalls = new();
public static async Task WsHandler(HttpContext context, System.Net.WebSockets.WebSocket webSocket, IQueryCollection query)
{
@ -168,6 +169,39 @@ public class Utils
{
await webSocket.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
// Check if user was in any call and remove them
foreach (var kvp in ActiveCalls)
{
bool removed = false;
lock (kvp.Value)
{
if (kvp.Value.Contains(id))
{
kvp.Value.Remove(id);
removed = true;
}
}
if (removed)
{
string callState;
lock (kvp.Value) { callState = JsonSerializer.Serialize(kvp.Value, AppJsonSerializerContext.Default.ListString); }
// Broadcast to all room members
string[] members = Fs.ReadDirectory($"{ROOMS_DIR}/dms/{DOMAIN}/{kvp.Key}/members");
if (members.Length == 0) members = Fs.ReadDirectory($"{ROOMS_DIR}/groups/{DOMAIN}/{kvp.Key}/members");
foreach (string memberWD in members)
{
if (Account.Utils.IsUserLocal(memberWD, out string _))
{
string memberId = Account.Utils.GetValidIdOrZero(memberWD);
_ = SendWsMessage(memberId, $"call_update:{kvp.Key}:{callState}");
}
}
}
}
}
}
@ -265,7 +299,13 @@ public class Utils
if (parts.Length == 2)
{
var key = parts[0].Trim();
var val = parts[1].Trim();
var val = parts[1];
int commentIndex = val.IndexOf('#');
if (commentIndex >= 0)
{
val = val.Substring(0, commentIndex);
}
val = val.Trim();
Environment.SetEnvironmentVariable(key, val);
}

View file

@ -20,7 +20,13 @@ public class Utils
if (parts.Length == 2)
{
var key = parts[0].Trim();
var val = parts[1].Trim();
var val = parts[1];
int commentIndex = val.IndexOf('#');
if (commentIndex >= 0)
{
val = val.Substring(0, commentIndex);
}
val = val.Trim();
Environment.SetEnvironmentVariable(key, val);
}