Bennyboy1973
Bennyboy1973

Reputation: 4256

Detecting when a user leaves (i.e. by closing their browser)

I have a Singleton service, GameServer which keeps track of guests to a particular page, a Game.razor page, and a Scoped service, GameBridge, between them. Gamebridge is injected into Game.razor, and GameServer is injected into Gamebridge.

GameServer sends out a ping event, which is relayed by GameBridge to the page. The page then calls a method in Gamebridge, which is relayed to GameServer, and sets a LastSeen DateTime to DateTime.Now for the user.

The idea was that if the user closes their browser, the Scoped service would be disposed, and the GameServer would no longer get update pings from the user, i.e. the user has timed out, and games can be forfeited, game rooms abandoned and so on.

The problem is that even if the browser is closed, Game.razor and GameBridge keep chugging along like everything's fine-- which I can see because the time value for the missing user keeps updating. As of right now, I cannot detect the user disconnection even when they close the browser in blazor server side.

Upvotes: 1

Views: 2634

Answers (1)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30410

My solution hooks into the browser window beforeunload event, and uses a Guid to track SPA sessions.

This is my demo system which you an adapt to fit your code.

Add a Singleton Service and register it.

using System;

namespace Blazor.App.Services
{
    public class SessionTrackerService
    {
        // Session List, blah, blah, ..

        public void NotifyNewSession(Guid ID)
        {
            //  Do whatever you want
            Debug.WriteLine($"Session {ID} Registered");
        }

        public void NotifySessionClosed(Guid ID)
        {
            var x = 0;
            Debug.WriteLine($"Session {ID} Closed");
            // Do re-registering
        }
    }
}

site.js registered in base page.

window.blazor_setExitEvent = function (dotNetHelper) {
    blazor_dotNetHelper = dotNetHelper;
        window.addEventListener("beforeunload", blazor_SetSPAClosed);
}

var blazor_dotNetHelper;

window.blazor_SetSPAClosed = function() {
    blazor_dotNetHelper.invokeMethodAsync('SPASessionClosed')
    blazor_dotNetHelper.dispose();
}

Add some code to App.razor. Cascade the SessionID if you need to use it elsewhere. Call window.blazor_setExitEvent to set the window beforeunload event and pass it a reference to App.

<CascadingValue Name="SessionID" Value="this.ID">
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        .....
    </Router>
</CascadingValue>

@code {
    [Inject] private IJSRuntime _js { get; set; }
    [Inject] private SessionTrackerService sessionTrackerService { get; set; }
    public Guid ID { get; set; } = Guid.NewGuid();

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            sessionTrackerService.NotifyNewSession(ID);
            var dotNetReference = DotNetObjectReference.Create(this);
            await _js.InvokeVoidAsync("window.blazor_setExitEvent", dotNetReference);
        }
    }

    [JSInvokable("SPASessionClosed")]
    public void SPASessionClosed()
    {
        sessionTrackerService.NotifySessionClosed(ID);
    }

I tested in on Edge, Chrome and Firefox.

Upvotes: 3

Related Questions