mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Extensions;
|
||||
using RadicadeServer.RecNet.Accounts;
|
||||
using RadicadeServer.RecNet.Notifications;
|
||||
using RadicadeServer.RecNet.Enums.Messages;
|
||||
using RadicadeServer.Utils;
|
||||
|
||||
namespace RadicadeServer.API.Notifications
|
||||
{
|
||||
public class NotifyHub : Hub
|
||||
{
|
||||
private static readonly string[] TokenCarriers = { "access_token", "accessToken", "token", "id_token" };
|
||||
|
||||
private RecDBContext db;
|
||||
public NotifyHub(IHubContext<NotifyHub> hubContext, RecDBContext db) {
|
||||
Notify.Context = hubContext;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private string? RawToken()
|
||||
{
|
||||
var httpContext = Context.GetHttpContext();
|
||||
if (httpContext == null) return null;
|
||||
|
||||
foreach (var name in TokenCarriers)
|
||||
{
|
||||
var fromQuery = httpContext.Request.Query[name].ToString();
|
||||
if (!string.IsNullOrWhiteSpace(fromQuery)) return fromQuery;
|
||||
}
|
||||
|
||||
var header = httpContext.Request.Headers["Authorization"].ToString();
|
||||
if (!string.IsNullOrWhiteSpace(header)) return header;
|
||||
|
||||
foreach (var name in TokenCarriers)
|
||||
{
|
||||
if (httpContext.Request.Cookies.TryGetValue(name, out var cookie) && !string.IsNullOrWhiteSpace(cookie))
|
||||
return cookie;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private long? GetAccountId()
|
||||
{
|
||||
var authenticatedAccount = Context.User?.GetUserId();
|
||||
if (authenticatedAccount != null) return authenticatedAccount;
|
||||
|
||||
var raw = RawToken();
|
||||
return string.IsNullOrWhiteSpace(raw) ? null : raw.GetUserId();
|
||||
}
|
||||
|
||||
private static JsonElement RawJson(string json) => JsonDocument.Parse(json).RootElement.Clone();
|
||||
|
||||
private async Task SendNotificationToCaller(string id, object msg)
|
||||
{
|
||||
var notification = new Notification { Id = id, Msg = msg };
|
||||
await Clients.Caller.SendAsync("Notification", notification.ToJson());
|
||||
}
|
||||
|
||||
private static List<long> ReadPlayerIds(object? update)
|
||||
{
|
||||
var ids = new List<long>();
|
||||
|
||||
void Add(JsonElement element)
|
||||
{
|
||||
if (element.ValueKind == JsonValueKind.Number && element.TryGetInt64(out var single))
|
||||
{
|
||||
ids.Add(single);
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.ValueKind == JsonValueKind.String && long.TryParse(element.GetString(), out single))
|
||||
{
|
||||
ids.Add(single);
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.ValueKind != JsonValueKind.Array) return;
|
||||
|
||||
foreach (var entry in element.EnumerateArray())
|
||||
{
|
||||
if (entry.ValueKind == JsonValueKind.Number && entry.TryGetInt64(out var value))
|
||||
ids.Add(value);
|
||||
else if (entry.ValueKind == JsonValueKind.String && long.TryParse(entry.GetString(), out value))
|
||||
ids.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (update is SubscriptionUpdate subscription && subscription.PlayerIds != null)
|
||||
{
|
||||
ids.AddRange(subscription.PlayerIds);
|
||||
return ids;
|
||||
}
|
||||
|
||||
if (update is not JsonElement json) return ids;
|
||||
|
||||
if (json.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
Add(json);
|
||||
return ids;
|
||||
}
|
||||
|
||||
if (json.ValueKind != JsonValueKind.Object) return ids;
|
||||
|
||||
foreach (var property in json.EnumerateObject())
|
||||
{
|
||||
if (!property.Name.Equals("PlayerIds", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
Add(property.Value);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
[HubMethodName("SubscribeToPlayers")]
|
||||
public Task Subscribe(object? update) => ApplySubscriptions(update);
|
||||
|
||||
[HubMethodName("playerSubscriptions/v1/update")]
|
||||
public Task SubscribeToPlayersV1(object? update) => ApplySubscriptions(update);
|
||||
|
||||
private async Task ApplySubscriptions(object? update)
|
||||
{
|
||||
long? account = GetAccountId();
|
||||
if (account == null) return;
|
||||
|
||||
var ids = ReadPlayerIds(update);
|
||||
|
||||
var client = Notify.GetConnectionClient(Context.ConnectionId);
|
||||
if (client != null)
|
||||
{
|
||||
lock (client.SubscriptionsLock)
|
||||
{
|
||||
client.Subscriptions = ids.Where(id => id > 0).Distinct().ToList();
|
||||
client.HasSubscriptions = true;
|
||||
}
|
||||
await Notify.UpdateSubscriptionsForClient(Context.ConnectionId);
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("SubscribeTo")]
|
||||
public Task SubscribeToChannels(object? update) => ApplyChannelSubscriptions(update, subscribing: true);
|
||||
|
||||
[HubMethodName("UnsubscribeFrom")]
|
||||
public Task UnsubscribeFromChannels(object? update) => ApplyChannelSubscriptions(update, subscribing: false);
|
||||
|
||||
private Task ApplyChannelSubscriptions(object? update, bool subscribing)
|
||||
{
|
||||
var channels = ReadChannels(update);
|
||||
if (channels.Count == 0) return Task.CompletedTask;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static List<string> ReadChannels(object? update)
|
||||
{
|
||||
var channels = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
if (update is not JsonElement json || json.ValueKind != JsonValueKind.Object) return channels;
|
||||
|
||||
foreach (var property in json.EnumerateObject())
|
||||
{
|
||||
if (!property.Name.Equals("Channels", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
if (property.Value.ValueKind != JsonValueKind.Array) continue;
|
||||
|
||||
foreach (var entry in property.Value.EnumerateArray())
|
||||
{
|
||||
var channel = entry.ValueKind == JsonValueKind.String ? entry.GetString() : null;
|
||||
if (!string.IsNullOrWhiteSpace(channel)) channels.Add(channel!);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
[HubMethodName("heartbeat2")]
|
||||
public Task<object?> Heartbeat() => AnswerHeartbeat();
|
||||
|
||||
[HubMethodName("heartbeat")]
|
||||
public Task<object?> HeartbeatUnversioned() => AnswerHeartbeat();
|
||||
|
||||
private async Task<object?> AnswerHeartbeat()
|
||||
{
|
||||
var account = GetAccountId();
|
||||
if (account == null) return null;
|
||||
|
||||
var presence = await Notify.BuildSelfPresenceAsync(db, account.Value);
|
||||
if (presence == null) return null;
|
||||
|
||||
await SendNotificationToCaller("PresenceUpdate", presence);
|
||||
|
||||
return RawJson($"{{\"Id\":{account.Value},\"Msg\":{presence.ToJson()}}}");
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var httpContext = Context.GetHttpContext();
|
||||
long? account = GetAccountId();
|
||||
var tokenCarrier = string.IsNullOrWhiteSpace(RawToken()) ? "none" : "present";
|
||||
|
||||
|
||||
if (account != null)
|
||||
{
|
||||
Notify.RegisterContext(Context);
|
||||
|
||||
lock (Notify.ClientsLock) Notify.Clients.Add(new HubClient
|
||||
{
|
||||
AccountId = account.Value,
|
||||
ConnectionId = Context.ConnectionId
|
||||
});
|
||||
|
||||
var activePresence = await db.Presences.FirstOrDefaultAsync(x => x.playerId == account.Value);
|
||||
if (activePresence != null && !activePresence.isOnline && activePresence.roomInstance.HasValue)
|
||||
{
|
||||
activePresence.isOnline = true;
|
||||
await db.SaveChangesAsync();
|
||||
await Notify.UpdateSubscriptions(account.Value);
|
||||
}
|
||||
|
||||
try { await Notify.SendSelfAccountUpdate(account.Value); }
|
||||
catch (Exception) { }
|
||||
|
||||
try
|
||||
{
|
||||
var presence = await Notify.BuildSelfPresenceAsync(db, account.Value);
|
||||
if (presence != null) await SendNotificationToCaller("PresenceUpdate", presence);
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var ban = await db.ResolveBanAsync(account.Value);
|
||||
if (ban != null)
|
||||
{
|
||||
var notification = new Notification { Id = NotificationType.ModerationKick, Msg = await PlayerUtils.BuildBanBlockDetailsAsync(db, ban, account.Value) };
|
||||
await Clients.Caller.SendAsync("Notification", notification.ToJson());
|
||||
}
|
||||
} catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var pendingWarning = await db.GetPendingWarningAsync(account.Value);
|
||||
if (pendingWarning != null)
|
||||
{
|
||||
await PlayerUtils.DeliverWarningAsync(account.Value,
|
||||
PlayerUtils.BuildWarningBlockDetails(pendingWarning, (int)PlayerUtils.WarningScreenHold.TotalSeconds));
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
var userId = GetAccountId();
|
||||
|
||||
lock (Notify.ClientsLock) Notify.Clients.RemoveAll(x => x.ConnectionId == Context.ConnectionId);
|
||||
Notify.ForgetContext(Context.ConnectionId);
|
||||
|
||||
if (userId != null)
|
||||
{
|
||||
bool stillConnected;
|
||||
lock (Notify.ClientsLock)
|
||||
stillConnected = Notify.Clients.Any(x => x.AccountId == userId.Value);
|
||||
|
||||
if (!stillConnected)
|
||||
{
|
||||
var presence = await db.Presences.FirstOrDefaultAsync(x => x.playerId == userId.Value);
|
||||
lock (Notify.ClientsLock)
|
||||
stillConnected = Notify.Clients.Any(x => x.AccountId == userId.Value);
|
||||
if (presence != null && !stillConnected)
|
||||
{
|
||||
presence.isOnline = false;
|
||||
await db.SaveChangesAsync();
|
||||
await Notify.UpdateSubscriptions(userId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user