I'm working on a Blazor Server app and need to detect when a user actually leaves the application (e.g. closes the browser/tab or loses connection), not just when they navigate between pages.
As you know, each navigation within a Blazor Server app tears down the current circuit and opens a new one — which also triggers OnConnectionDownAsync() and OnConnectionUpAsync() in a custom CircuitHandler. This makes it hard to know whether the user simply navigated or truly disconnected. To solve this, I’m trying the following approach:
Proposed approach: Use a timeout in OnConnectionDownAsync to delay any "user disconnected" logic, and cancel the timeout if a new circuit connects shortly afterward. Here’s the code:
private readonly ConcurrentDictionary<string, Timer> _disconnectTimers = new();
private readonly ConcurrentHashSet<string> _activeCircuits = new(); // or use ConcurrentDictionary if needed
public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
{
var timer = new Timer(_ =>
{
// Check if user has not reconnected
if (!_activeCircuits.Contains(circuit.Id))
{
_userTracker.MarkUserAsOffline(circuit.User);
}
}, null, TimeSpan.FromSeconds(5), Timeout.InfiniteTimeSpan);
_disconnectTimers[circuit.Id] = timer;
return Task.CompletedTask;
}
public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
_activeCircuits.Add(circuit.Id);
if (_disconnectTimers.TryRemove(circuit.Id, out var timer))
{
timer.Dispose();
}
_userTracker.MarkUserAsOnline(circuit.User);
return Task.CompletedTask;
}
Question: Does this approach make sense for distinguishing between user navigation and actual disconnects in Blazor Server?
Are there any edge cases or race conditions I should watch out for?
Is there a better pattern for handling this kind of circuit tracking?
I’d appreciate feedback or improvements from anyone who’s dealt with similar scenarios.