tpeczek / Lib.AspNetCore.ServerSentEvents

Lib.AspNetCore.ServerSentEvents is a library which provides Server-Sent Events (SSE) support for ASP.NET Core
MIT License
304 stars 35 forks source link

Single client clarity... #41

Closed leijae closed 3 years ago

leijae commented 3 years ago

After reading the documentation, I see that the User is exposed as well as the SendEventAsync. However, sending to a single client does seem to be a little more problematic for me.

Things I've tried:

  1. Getting a single connected client, selecting a client then sending an event:

    var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier));
    _eventService.GetClient(userId).SendEventAsync("some message");
  2. Getting a client from a list of connected Clients:

    var user = User;
    _eventService.GetClients().Single(x => x.User == user).SendEventAsync("some message");

both of these result, for me, in a null reference error even though my unit tests show that get clients is populated with the existing claimsprincipal that defines User.

As a less secure work-around I'm just doing:

            var sse = new ServerSentEvent
            {
                Id = userId.ToString(),
                Type = messageType,
                Data = new List<string>{"some message"}
            };

return _seventService.SendEventAsync(sse); // TO ALL CLIENTS - on the server

and on the client,

        var source = new EventSource('/message-channel');
        source.onmessage = function(event) {
            console.log(event.data);
        }

        source.addEventListener('messageType', function (event) {
            alert(event.data);
        });

What is actually the CORRECT way of doing this?

tpeczek commented 3 years ago

Hi @leijae,

You first approach doesn't work, because IServerSentEventsService.GetClient expects ServerSentEventsClient.Id as a value.

If it comes to the second approach, I'm guessing you are doing this in context of a different request then the one which has established the connection. In that case, the User is in fact a different object just with the same values.

The correct approach is to use something unique from your claims to query for the correct client. I don't know the nature of your claims enough to give you 100% correct suggestion, but assuming your first approach comes from fact that you do have a unique value in name identifier, it could be something like this.

var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
_eventService.GetClients()
    .Single(x => x.User.FindFirstValue(ClaimTypes.NameIdentifier) == userId)
    .SendEventAsync("some message");

I also wouldn't be so brave to use Single unless you are really sure that user has an active SSE connection. I would rather go for null check and some logging.

leijae commented 3 years ago

Ok fantastic. Thanks for the clarity. Just wanted to show you what I'm doing here. With your help I did manage to get it working.

I have a bot using the MS Bot Framework, if a user wants to escalate to an agent they send a request goes to an available agent. So, using SSE, an agent can be anywhere in the application and get the request.

MS did a good job with MS Bot Framework, but they made using the bot as proxy concept incredibly difficult in practice...

image

tpeczek commented 3 years ago

Happy to hear that you've managed to get it working and even more happy that you are using this library for interesting stuff :+1:.