attempt to fix notifications for ConsumableMappingAdded

it didnt work
This commit is contained in:
Lapis
2026-04-05 11:52:27 -04:00
parent 4004bdb1b9
commit ceda1be433
8 changed files with 476 additions and 154 deletions
+10 -1
View File
@@ -28,7 +28,11 @@ public class NotificationsHub : Hub
public async Task SubscribeToPlayers(SubscriptionList subscriptionList)
{
await _notificationService.SubscribeToPlayers(Context.ConnectionId, subscriptionList);
var hubContext = GetService<IHubContext<NotificationsHub>>();
if (hubContext != null)
{
await _notificationService.SubscribeToPlayers(Context.ConnectionId, subscriptionList, hubContext);
}
}
public Task<HashSet<int>> GetSubscriptions()
@@ -40,4 +44,9 @@ public class NotificationsHub : Hub
{
return _notificationService;
}
private T? GetService<T>() where T : class
{
return Context.GetHttpContext()?.RequestServices.GetService(typeof(T)) as T;
}
}
+16 -1
View File
@@ -17,6 +17,21 @@ public class BuyItemRequest
public int? CouponConsumablePlayerMappingId { get; set; }
[JsonPropertyName("Gift")]
public object? Gift { get; set; }
public GiftRequest? Gift { get; set; }
}
public class GiftRequest
{
[JsonPropertyName("ToPlayerId")]
public int ToPlayerId { get; set; }
[JsonPropertyName("Message")]
public string Message { get; set; } = string.Empty;
[JsonPropertyName("Anonymous")]
public bool Anonymous { get; set; }
[JsonPropertyName("GiftContext")]
public int GiftContext { get; set; }
}
@@ -8,6 +8,7 @@ public class NotificationMessage
public string? NotificationId { get; set; }
[JsonPropertyName("notificationType")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public PushNotificationId? NotificationType { get; set; }
[JsonPropertyName("id")]
+59 -39
View File
@@ -1353,15 +1353,26 @@ public class APIController
db.TokenBalances.Update(tokenBalance);
await db.SaveChangesAsync();
// Store each gift in the database as a received gift with all properties
var receiverAccountId = id;
int? senderPlayerId = null;
if (buyRequest.Gift != null)
{
receiverAccountId = buyRequest.Gift.ToPlayerId;
if (!buyRequest.Gift.Anonymous)
{
senderPlayerId = id;
}
}
var storedGifts = new List<ReceivedGift>();
foreach (var giftDrop in storefrontItem.GiftDrops)
{
var receivedGift = new ReceivedGift
{
ReceiverAccountId = id,
FromPlayerId = null,
Message = "A gift for you <3",
ReceiverAccountId = receiverAccountId,
FromPlayerId = senderPlayerId,
Message = buyRequest.Gift?.Message ?? "A gift for you <3",
ConsumableItemDesc = giftDrop.ConsumableItemDesc ?? string.Empty,
AvatarItemDesc = giftDrop.AvatarItemDesc ?? string.Empty,
FriendlyName = giftDrop.FriendlyName ?? string.Empty,
@@ -1375,7 +1386,7 @@ public class APIController
Platform = -1,
PlatformsToSpawnOn = -1,
BalanceType = 0,
GiftContext = giftDrop.Context,
GiftContext = buyRequest.Gift?.GiftContext ?? giftDrop.Context,
GiftRarity = giftDrop.Rarity,
ReceivedAt = DateTime.UtcNow,
IsConsumed = false
@@ -1386,7 +1397,6 @@ public class APIController
}
await db.SaveChangesAsync();
// Build the response with stored gift IDs
var giftData = storedGifts.Select(rg => new GiftData
{
Id = rg.Id,
@@ -1424,11 +1434,7 @@ public class APIController
Platform = -1
};
// Send notification for purchase
try
{
var hubContext = app.Services.GetService<IHubContext>();
if (hubContext != null)
{
var purchaseNotification = notificationService.CreateNotification(
PushNotificationId.StorefrontBalancePurchase,
@@ -1442,10 +1448,13 @@ public class APIController
{ "NewBalance", tokenBalance.Balance }
}
);
await notificationService.SendNotificationToPlayer(null, hubContext, id, purchaseNotification);
await notificationService.SendNotificationToPlayer(id, purchaseNotification);
}
catch (Exception notifEx)
{
Console.WriteLine($"Error sending purchase notification: {notifEx.Message}");
}
catch { }
return Results.Ok(response);
}
@@ -1501,7 +1510,6 @@ public class APIController
}
}
// Get all earnable rewards for this context
var earnableRewards = await db.EarnableRewards
.Where(er => er.RewardContext == giftContext)
.ToListAsync();
@@ -1515,30 +1523,25 @@ public class APIController
int giftRarity = 0;
string consumableItemDesc = "";
// 60/40 roll - 60% earnable item, 40% tokens
bool isEarnableItem = random.Next(0, 100) < 60;
if (isEarnableItem && earnableRewards.Any())
{
// Get rewards that are avatar items (have avatarItemDesc set)
var avatarRewards = earnableRewards.Where(er => !string.IsNullOrEmpty(er.AvatarItemDesc)).ToList();
if (avatarRewards.Any())
{
// Get all avatar items the player currently owns
var ownedAvatarItems = await db.AvatarItems
.Where(ai => ai.OwnerAccountId == id)
.Select(ai => ai.AvatarItemDesc)
.ToListAsync();
// Filter to rewards the player doesn't own
var unownedRewards = avatarRewards
.Where(ar => !ownedAvatarItems.Contains(ar.AvatarItemDesc))
.ToList();
if (unownedRewards.Any())
{
// Randomly select one of the unowned rewards
var selectedReward = unownedRewards[random.Next(unownedRewards.Count)];
avatarItemDesc = selectedReward.AvatarItemDesc;
friendlyName = selectedReward.FriendlyName;
@@ -1547,28 +1550,23 @@ public class APIController
}
else
{
// Player owns all avatar items, give token box instead
isEarnableItem = false;
}
}
else
{
// No avatar rewards, give token box instead
isEarnableItem = false;
}
}
// If not earnable item or player owns all items, generate a token box
if (!isEarnableItem)
{
int[] tokenAmounts = { 10, 25, 50, 100, 250, 500 };
int[] tokenAmounts = {10, 25, 50, 100, 250, 500};
currency = tokenAmounts[random.Next(tokenAmounts.Length)];
currencyType = 2;
consumableItemDesc = $"TokenBox_{currency}";
giftRarity = 20;
}
// Create the received gift record
var receivedGift = new ReceivedGift
{
ReceiverAccountId = id,
@@ -1640,7 +1638,6 @@ public class APIController
if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
// Parse form data
request.EnableBuffering();
request.Body.Position = 0;
using var reader = new StreamReader(request.Body);
@@ -1669,7 +1666,6 @@ public class APIController
return Results.BadRequest(new { success = false, error = "Invalid gift ID" });
}
// Look for the ReceivedGift
var receivedGift = await db.ReceivedGifts
.FirstOrDefaultAsync(rg => rg.Id == giftId && rg.ReceiverAccountId == id);
@@ -1678,7 +1674,6 @@ public class APIController
return Results.NotFound(new { success = false, error = "Gift not found" });
}
// Process the gift based on its properties
if (!string.IsNullOrEmpty(receivedGift.ConsumableItemDesc))
{
var existingConsumable = await db.ConsumableItems
@@ -1720,18 +1715,38 @@ public class APIController
db.AvatarItems.Add(avatarItem);
}
// Mark the gift as consumed
if (!string.IsNullOrEmpty(receivedGift.Currency.ToString()) && receivedGift.CurrencyType == 2)
{
var tokenBalance = await db.TokenBalances
.FirstOrDefaultAsync(tb => tb.Id == id &&
tb.CurrencyType == receivedGift.CurrencyType &&
tb.BalanceType == -1);
if (tokenBalance == null)
{
tokenBalance = new TokenBalance
{
Id = id,
CurrencyType = receivedGift.CurrencyType,
BalanceType = -1,
Balance = receivedGift.Currency
};
db.TokenBalances.Add(tokenBalance);
}
else
{
tokenBalance.Balance += receivedGift.Currency;
db.TokenBalances.Update(tokenBalance);
}
}
receivedGift.IsConsumed = true;
receivedGift.ConsumedAt = DateTime.UtcNow;
db.ReceivedGifts.Update(receivedGift);
await db.SaveChangesAsync();
// Send notifications based on what was added
try
{
var hubContext = app.Services.GetService<IHubContext>();
if (hubContext != null)
{
if (!string.IsNullOrEmpty(receivedGift.ConsumableItemDesc))
{
@@ -1741,22 +1756,27 @@ public class APIController
toAccountId: id,
data: new Dictionary<string, object>
{
{ "Id", (long)giftId },
{ "ConsumableItemDesc", receivedGift.ConsumableItemDesc },
{ "PlatformMask", -1 },
{ "CreatedAt", DateTime.UtcNow },
{ "Id", giftId },
{ "ConsumableType", "JfnVXFmilU6ysv-VbTAe3A" },
{ "PlatformMask", 4294967295 },
{ "Count", 1 },
{ "InitialCount", 1 },
{ "UnlockedLevel", 0 },
{ "CreatedAt", DateTime.UtcNow },
{ "IsActive", false },
{ "ActiveDurationMinutes", null }
{ "ActiveDurationMinutes", null },
{ "Category", 0 },
{ "IsPlatformLocked", false }
}
);
await notificationService.SendNotificationToPlayer(null, hubContext, id, consumableNotification);
await notificationService.SendNotificationToPlayer(id, consumableNotification);
}
}
catch (Exception notifEx)
{
// Log the error but don't fail the gift consumption
Console.WriteLine($"Error sending notification: {notifEx.Message}");
}
catch { }
return Results.Ok(new { success = true });
}
@@ -16,7 +16,7 @@ public class AccountsController
return Results.Ok("FUCKL MYSJDIOJD");
});
app.MapGet("/account/me", (HttpRequest request, AppDbContext db) =>
app.MapGet("/account/me", async (HttpRequest request, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
@@ -32,17 +32,21 @@ public class AccountsController
if (!int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
var account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
var selfAccount = new SelfAccount
{
AccountId = id,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{id}",
DisplayName = $"Player{id}",
CreatedAt = DateTime.UtcNow,
ProfileImage = account.ProfileImage ?? "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = account.IsJunior,
Platforms = account.Platforms ?? 0,
PersonalPronouns = account.PersonalPronouns ?? 0,
IdentityFlags = account.IdentityFlags ?? 0,
Username = account.Username ?? $"Player{id}",
DisplayName = account.DisplayName ?? $"Player{id}",
CreatedAt = account.CreatedAt,
Email = null,
Phone = null,
JuniorState = null,
@@ -54,54 +58,62 @@ public class AccountsController
return Results.Ok(selfAccount);
});
app.MapGet("/account/bulk", (HttpRequest request) =>
app.MapGet("/account/bulk", async (HttpRequest request, AppDbContext db) =>
{
var ids = request.Query["id"];
var accounts = new List<Account>();
var accountIds = new List<int>();
foreach (var id in ids)
{
if (int.TryParse(id, out var accountId))
{
accounts.Add(new Account
{
AccountId = accountId,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
});
accountIds.Add(accountId);
}
}
return Results.Json(accounts);
var accounts = await db.Accounts
.Where(a => accountIds.Contains(a.AccountId.Value))
.ToListAsync();
var result = accounts.Select(a => new Account
{
AccountId = a.AccountId,
ProfileImage = a.ProfileImage ?? "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = a.IsJunior,
Platforms = a.Platforms ?? 0,
PersonalPronouns = a.PersonalPronouns ?? 0,
IdentityFlags = a.IdentityFlags ?? 0,
Username = a.Username ?? $"Player{a.AccountId}",
DisplayName = a.DisplayName ?? $"Player{a.AccountId}",
CreatedAt = a.CreatedAt
}).ToList();
return Results.Json(result);
});
app.MapGet("/account/{id}", (HttpRequest request, string id) =>
app.MapGet("/account/{id}", async (HttpRequest request, string id, AppDbContext db) =>
{
var accounts = new Account();
if (!int.TryParse(id, out var accountId))
return Results.BadRequest();
if (int.TryParse(id, out var accountId))
var account = await db.Accounts.FindAsync(accountId);
if (account == null)
return Results.NotFound();
var result = new Account
{
accounts = new Account
{
AccountId = accountId,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
AccountId = account.AccountId,
ProfileImage = account.ProfileImage ?? "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = account.IsJunior,
Platforms = account.Platforms ?? 0,
PersonalPronouns = account.PersonalPronouns ?? 0,
IdentityFlags = account.IdentityFlags ?? 0,
Username = account.Username ?? $"Player{account.AccountId}",
DisplayName = account.DisplayName ?? $"Player{account.AccountId}",
CreatedAt = account.CreatedAt
};
}
return Results.Json(accounts);
return Results.Json(result);
});
app.MapPost("/account/create", async (HttpRequest httpRequest, AppDbContext db) =>
@@ -300,7 +312,146 @@ public class AccountsController
account.DisplayName = newDisplayName;
await db.SaveChangesAsync();
return Results.Ok();
return Results.Ok(RecNetResult.Ok());
});
app.MapPut("/account/me/username", async (HttpRequest request, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId))
return Results.Unauthorized();
if (!int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
string newAccountName = "";
if (request.ContentLength.HasValue && request.ContentLength > 0)
{
try
{
request.EnableBuffering();
using var reader = new StreamReader(request.Body, leaveOpen: true);
var body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body))
{
foreach (var pair in body.Split('&'))
{
var keyValue = pair.Split('=');
if (keyValue.Length == 2)
{
var key = Uri.UnescapeDataString(keyValue[0]);
var value = Uri.UnescapeDataString(keyValue[1]);
if (key == "username")
newAccountName = value;
}
}
}
request.Body.Position = 0;
}
catch { }
}
var account = await db.Accounts.FindAsync(id);
if (account == null)
return Results.NotFound();
account.Username = newAccountName;
await db.SaveChangesAsync();
return Results.Ok(RecNetResult.Ok());
});
app.MapGet("/account/{id}/bio", async (HttpRequest request, string id, AppDbContext db) =>
{
if (!int.TryParse(id, out var accountId))
return Results.BadRequest();
var bio = await db.PlayerBios.FirstOrDefaultAsync(b => b.accountId == accountId);
if (bio == null)
{
return Results.Json(new { accountId = accountId, bio = "" });
}
return Results.Json(new { accountId = bio.accountId, bio = bio.bio });
});
app.MapPut("/account/me/bio", async (HttpRequest request, AppDbContext db) =>
{
var authHeader = request.Headers.Authorization.ToString();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
return Results.Unauthorized();
var token = authHeader.Substring("Bearer ".Length);
var accountId = jwtService.ValidateAndGetAccountId(token);
if (string.IsNullOrEmpty(accountId))
return Results.Unauthorized();
if (!int.TryParse(accountId.AsSpan(), out var id))
return Results.Unauthorized();
string newBio = "";
if (request.ContentLength.HasValue && request.ContentLength > 0)
{
try
{
request.EnableBuffering();
using var reader = new StreamReader(request.Body, leaveOpen: true);
var body = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(body))
{
foreach (var pair in body.Split('&'))
{
var keyValue = pair.Split('=');
if (keyValue.Length == 2)
{
var key = Uri.UnescapeDataString(keyValue[0]);
var value = Uri.UnescapeDataString(keyValue[1]);
if (key == "bio")
newBio = Uri.UnescapeDataString(value);
}
}
}
request.Body.Position = 0;
}
catch { }
}
var bio = await db.PlayerBios.FirstOrDefaultAsync(b => b.accountId == id);
if (bio == null)
{
bio = new PlayerBio
{
accountId = id,
bio = newBio
};
db.PlayerBios.Add(bio);
}
else
{
bio.bio = newBio;
db.PlayerBios.Update(bio);
}
await db.SaveChangesAsync();
return Results.Json(new { success = true });
});
}
}
@@ -17,32 +17,39 @@ public class MatchmakingController
app.MapPost("/player/login", () => Results.Ok());
app.MapGet("/player", (HttpRequest request) =>
app.MapGet("/player", async (HttpRequest request, AppDbContext db) =>
{
/*
var id = request.Query["id"];
var accounts = new List<Account>();
if (int.TryParse(id, out var accountId))
var id = request.Query["id"].FirstOrDefault();
if (string.IsNullOrEmpty(id) || !int.TryParse(id, out var accountId))
{
accounts.Add(new Account
{
AccountId = accountId,
ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = false,
Platforms = 0,
PersonalPronouns = 0,
IdentityFlags = 0,
Username = $"Player{accountId}",
DisplayName = $"Player{accountId}",
CreatedAt = DateTime.UtcNow
});
}
return Results.Json(accounts);*/
var json = File.ReadAllText("JSON/getplayer.json");
return Results.Content(json, "application/json");
}
var account = await db.Accounts.FindAsync(accountId);
if (account == null)
{
var json = File.ReadAllText("JSON/getplayer.json");
return Results.Content(json, "application/json");
}
var result = new List<Account>
{
new Account
{
AccountId = account.AccountId,
ProfileImage = account.ProfileImage ?? "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg",
IsJunior = account.IsJunior,
Platforms = account.Platforms ?? 0,
PersonalPronouns = account.PersonalPronouns ?? 0,
IdentityFlags = account.IdentityFlags ?? 0,
Username = account.Username ?? $"Player{account.AccountId}",
DisplayName = account.DisplayName ?? $"Player{account.AccountId}",
CreatedAt = account.CreatedAt
}
};
return Results.Json(result);
});
app.MapPost("/goto/room/{room}", async (HttpRequest request, string room, AppDbContext db) =>
@@ -1,5 +1,6 @@
using CannedNet.Hubs;
using CannedNet.Services.Infrastructure;
using Microsoft.AspNetCore.SignalR;
namespace CannedNet.Services.Controllers;
@@ -10,5 +11,8 @@ public class NotifyController
public void MapEndpoints(WebApplication app)
{
app.MapHub<NotificationsHub>("/hub/v1");
var hubContext = app.Services.GetRequiredService<IHubContext<NotificationsHub>>();
NotificationService.SetHubContext(hubContext);
}
}
+130 -15
View File
@@ -1,26 +1,39 @@
using System.Collections.Concurrent;
using System.Text;
using System.Text.Json;
using CannedNet.Hubs;
using Microsoft.AspNetCore.SignalR;
namespace CannedNet.Services;
public class NotificationService
{
private readonly ConcurrentDictionary<string, HashSet<int>> _connectionSubscriptions = new();
private readonly ConcurrentDictionary<int, HashSet<string>> _playerConnections = new();
private static readonly ConcurrentDictionary<string, HashSet<int>> _connectionSubscriptions = new();
private static readonly ConcurrentDictionary<int, HashSet<string>> _playerConnections = new();
private static readonly ConcurrentDictionary<int, Queue<NotificationMessage>> _pendingNotifications = new();
private static IHubContext<NotificationsHub>? _hubContext;
private readonly ILogger<NotificationService> _logger;
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
public NotificationService(ILogger<NotificationService> logger)
{
_logger = logger;
}
public static void SetHubContext(IHubContext<NotificationsHub> hubContext)
{
_hubContext = hubContext;
}
public void OnConnected(string connectionId)
{
_logger.LogInformation($"Client connected: {connectionId}");
_connectionSubscriptions.TryAdd(connectionId, new HashSet<int>());
}
public void OnDisconnected(string connectionId)
{
_logger.LogInformation($"Client disconnected: {connectionId}");
if (_connectionSubscriptions.TryRemove(connectionId, out var subscriptions))
{
foreach (var playerId in subscriptions)
@@ -37,11 +50,13 @@ public class NotificationService
}
}
public Task SubscribeToPlayers(string connectionId, SubscriptionList subscriptionList)
public async Task SubscribeToPlayers(string connectionId, SubscriptionList subscriptionList, IHubContext<NotificationsHub> hubContext)
{
var oldSubscriptions = _connectionSubscriptions.GetValueOrDefault(connectionId, new HashSet<int>());
var newPlayerIds = subscriptionList.PlayerIds ?? new List<int>();
_logger.LogInformation($"Connection {connectionId} subscribing to players: {string.Join(", ", newPlayerIds)}");
_connectionSubscriptions[connectionId] = new HashSet<int>(newPlayerIds);
foreach (var playerId in newPlayerIds)
@@ -54,6 +69,22 @@ public class NotificationService
existing.Add(connectionId);
return existing;
});
if (_pendingNotifications.TryRemove(playerId, out var pendingQueue))
{
_logger.LogInformation($"Found {pendingQueue.Count} pending notifications for player {playerId}. Sending to connection {connectionId}");
while (pendingQueue.TryDequeue(out var notification))
{
try
{
await SendToConnection(hubContext, connectionId, notification);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error sending pending notification to player {playerId}");
}
}
}
}
foreach (var playerId in oldSubscriptions)
@@ -63,8 +94,6 @@ public class NotificationService
connections.Remove(connectionId);
}
}
return Task.CompletedTask;
}
public HashSet<int>? GetSubscriptions(string connectionId)
@@ -90,19 +119,26 @@ public class NotificationService
return Enumerable.Empty<int>();
}
public async Task SendNotificationToPlayer(string callerConnectionId, IHubContext hubContext, int playerId, NotificationMessage notification)
public async Task SendNotificationToPlayer(int playerId, NotificationMessage notification)
{
try
if (_hubContext == null)
{
var connections = GetConnectionsForPlayer(playerId);
foreach (var connectionId in connections)
{
if (connectionId == callerConnectionId)
continue;
_logger.LogWarning("No hub context available. Cannot send notification.");
return;
}
try
{
await hubContext.Clients.Client(connectionId).SendAsync("Notification", notification);
var connections = GetConnectionsForPlayer(playerId).ToList();
_logger.LogInformation($"Attempting to send notification to player {playerId}. Found {connections.Count} connections. NotificationType: {notification.NotificationType}");
if (connections.Any())
{
foreach (var connectionId in connections)
{
try
{
await SendToConnection(_hubContext, connectionId, notification);
}
catch (Exception ex)
{
@@ -110,17 +146,42 @@ public class NotificationService
}
}
}
else
{
_logger.LogWarning($"No connections found for player {playerId}. Queueing notification for later delivery. NotificationType: {notification.NotificationType}");
_pendingNotifications.AddOrUpdate(
playerId,
_ => new Queue<NotificationMessage>(new[] { notification }),
(_, queue) =>
{
queue.Enqueue(notification);
return queue;
});
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error sending notification to player {playerId}. NotificationType: {notification.NotificationType}");
}
}
public async Task BroadcastNotification(IHubContext hubContext, NotificationMessage notification)
public async Task BroadcastNotification(NotificationMessage notification)
{
if (_hubContext == null)
{
_logger.LogWarning("No hub context available. Cannot broadcast notification.");
return;
}
try
{
await hubContext.Clients.All.SendAsync("Notification", notification);
foreach (var kvp in _playerConnections)
{
foreach (var connectionId in kvp.Value)
{
await SendToConnection(_hubContext, connectionId, notification);
}
}
}
catch (Exception ex)
{
@@ -141,4 +202,58 @@ public class NotificationService
Data = data
};
}
private static async Task SendToConnection(IHubContext<NotificationsHub> hubContext, string connectionId, NotificationMessage notification)
{
var notificationData = BuildNotificationObject(notification);
try
{
var json = JsonSerializer.Serialize(notificationData);
System.Console.WriteLine($"[NotificationService] Sending notification to {connectionId}: {json}");
}
catch (Exception ex)
{
System.Console.WriteLine($"[NotificationService] Error serializing notification: {ex.Message}");
}
await hubContext.Clients.Client(connectionId).SendAsync("Notification", notificationData);
}
private static Dictionary<string, object?> BuildNotificationObject(NotificationMessage notification)
{
var dict = new Dictionary<string, object?>
{
{ "notificationId", notification.NotificationId ?? "" },
{ "notificationType", (int?)notification.NotificationType ?? 0 },
};
// Only add optional fields if they have values
if (notification.Id.HasValue)
dict.Add("id", notification.Id.Value);
if (notification.FromAccountId.HasValue)
dict.Add("fromAccountId", notification.FromAccountId.Value);
if (notification.ToAccountId.HasValue)
dict.Add("toAccountId", notification.ToAccountId.Value);
if (notification.CreatedAt.HasValue)
dict.Add("createdAt", notification.CreatedAt.Value.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
// Properly serialize the data dictionary, converting DateTime objects to ISO 8601 strings
var serializedData = new Dictionary<string, object>();
if (notification.Data != null && notification.Data.Count > 0)
{
foreach (var kvp in notification.Data)
{
if (kvp.Value is DateTime dt)
{
serializedData[kvp.Key] = dt.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
}
else if (kvp.Value != null)
{
serializedData[kvp.Key] = kvp.Value;
}
}
}
dict.Add("data", serializedData);
return dict;
}
}