GM.RealTime.Persistence 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package GM.RealTime.Persistence --version 1.0.0
                    
NuGet\Install-Package GM.RealTime.Persistence -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GM.RealTime.Persistence" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GM.RealTime.Persistence" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="GM.RealTime.Persistence" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GM.RealTime.Persistence --version 1.0.0
                    
#r "nuget: GM.RealTime.Persistence, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GM.RealTime.Persistence@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GM.RealTime.Persistence&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=GM.RealTime.Persistence&version=1.0.0
                    
Install as a Cake Tool

<p align="center"> <img src="https://raw.githubusercontent.com/gmetskhvarishvili/GM.RealTime/master/icon.png" alt="GM.RealTime" width="140" height="140" /> </p>

GM.RealTime

CI NuGet License: MIT

SignalR-based real-time communication for the GM.* ecosystem. Push to users, connections, and groups through a clean IRealTimeSender instead of wiring IHubContext<T> by hand; track presence in a shared, lock-guarded connection registry (backed by GM.Caching + GM.DistributedLock, so it's Redis-ready across nodes); and authenticate the WebSocket handshake with a JWT from the access_token query string (pairs with GM.Identity). Targets .NET 10.

Packages

The three packages version and release together (lockstep):

Package What it gives you
GM.RealTime IRealTimeSender, IRealTimeClient, INotificationHub, the NotificationHub, the JWT-from-query handshake, and AddGMRealTime() / MapGMRealTimeHub().
GM.RealTime.Domain Presence models (Connection, UserPresence, RealTimeMessage) and the IConnectionRegistry abstraction. No infrastructure dependencies.
GM.RealTime.Persistence CacheConnectionRegistry — the registry over ICacheService, with every read-modify-write guarded by IDistributedLock.
dotnet add package GM.RealTime

Quick start

using GM.RealTime;

// Presence is shared across nodes when the cache + lock are Redis-backed — register those first:
builder.Services.AddGMRedisCaching(o => o.ConnectionString = "localhost:6379");
builder.Services.AddGMRedisDistributedLock(o => o.ConnectionString = "localhost:6379");

builder.Services.AddGMRealTime(o => o.HubPath = "/hubs/realtime");

// JWT is validated by your existing setup (e.g. GM.Identity); GM.RealTime only teaches it to read
// the token from ?access_token=... on the WebSocket handshake.
builder.Services.AddAuthentication().AddJwtBearer(/* your issuer/audience/key */);

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGMRealTimeHub();   // maps NotificationHub at RealTimeOptions.HubPath
app.Run();

Without the Redis registrations, AddGMRealTime() falls back to the in-memory cache and lock (single-process) — perfect for development.

Push messages

Inject IRealTimeSender anywhere — no IHubContext in your app code:

public class OrderNotifier(IRealTimeSender realtime)
{
    public Task OrderShipped(string userId, Guid orderId) =>
        realtime.SendToUserAsync(userId, "order.shipped", new { orderId });
}

SendToUserAsync looks the user's live connections up in the shared registry, so it targets every device they have open — on any node. Also available: SendToConnectionAsync, SendToGroupAsync, SendToAllAsync.

Presence

public class PresenceEndpoint(IConnectionRegistry registry)
{
    public Task<bool> IsOnline(string userId) => registry.IsOnlineAsync(userId);
}

The NotificationHub records connects/disconnects automatically (keyed by the authenticated user), and mirrors JoinGroup / LeaveGroup into both SignalR and the registry.

Client contract

The hub is strongly typed (Hub<IRealTimeClient>), so clients listen for one method:

connection.on("ReceiveMessage", m => console.log(m.event, m.payload));

How presence stays correct under load

Every connect/disconnect is a read-modify-write on a user's connection set. Under a burst of concurrent connections (multiple tabs, reconnects) those would race and lose ids. The registry takes a per-user distributed lock (GM.DistributedLock) around each mutation, and stores the set in GM.Caching — so it is both race-free and shared across every server instance.

Scale-out note: the registry (presence, targeting) is shared via Redis out of the box. To also fan out hub messages across multiple servers, add the standard SignalR Redis backplane (Microsoft.AspNetCore.SignalR.StackExchangeRedis) alongside — the two are complementary.

Roadmap: a Herald (GM.Notifications) real-time channel

GM.RealTime is designed to drop into GM.Notifications as a new delivery channel next to Email / SMS / Push / Slack / WhatsApp. The sketch:

GM.Notifications.RealTime/
  IRealTimeSenderService : (channel contract, like IEmailSenderService)
  RealTimeSenderService  : wraps GM.RealTime's IRealTimeSender, mapping a
                           RealTimeNotification -> realtime.SendToUserAsync(userId, "notification", dto)
  AddRealTimeNotificationServices(this IServiceCollection)  // mirrors AddEmailNotificationServices
  • Add a RealTimeNotification : NotificationBase entity (channel = RealTime) in GM.Notifications.Domain, with a matching EF configuration in GM.Notifications.Persistence.
  • A RealTimeWorker (like the Email/SMS workers) polls pending RealTimeNotifications and calls IRealTimeSenderService.SendAsync, which delegates to IRealTimeSender.SendToUserAsync — marking the notification Sent, or Failed (with retry) if the user is offline, exactly like the other channels. Presence (IConnectionRegistry.IsOnlineAsync) lets the worker skip or defer delivery to offline users.

That layering keeps GM.RealTime standalone while making it a first-class Herald channel.

Repository layout

GM.RealTime/              # IRealTimeSender, hub, JWT handshake, AddGMRealTime / MapGMRealTimeHub
GM.RealTime.Domain/       # Connection, UserPresence, RealTimeMessage, IConnectionRegistry
GM.RealTime.Persistence/  # CacheConnectionRegistry (GM.Caching + GM.DistributedLock)
tests/GM.RealTime.Tests/  # xUnit tests for the connection registry

Building & testing

dotnet build -c Release
dotnet test  -c Release

Releasing

Versioning is automated from Conventional Commits — see CONTRIBUTING.md. All three packages share one version (Directory.Build.props) and publish together to nuget.org on each release.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on GM.RealTime.Persistence:

Package Downloads
GM.RealTime

SignalR-based real-time communication for the GM.* ecosystem. IRealTimeSender pushes to users/connections/groups without touching IHubContext directly; a presence-tracking hub registers connections in a shared, lock-guarded registry (GM.Caching + GM.DistributedLock); and the WebSocket handshake reads the JWT from the access_token query string (pairs with GM.Identity). One call: AddGMRealTime().

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 114 8/2/2026
1.0.0 113 8/2/2026