Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Azure SignalR Serverless mode, how to map connection ids to users?

All the documentation I've seen so far show how to map connection ids to users via the Hub's OnConnected/OnDisconnected methods. But how can I do this in a Serverless setup? Is it at all possible in Azure Functions?

I have implemented the negotiate endpoint, so I have access to SignalRConnectionInfo. I have a Send method with a SignalR output binding, to which I can add an instance of SignalRMessage, which then gets broadcast to all connected clients. The SignalRMessage class does have UserId and GroupName properties, so I believe it must be possible to specify a user or a group to send the message to.

like image 859
Ramesh Avatar asked Jul 23 '19 17:07

Ramesh


2 Answers

If you are not using Azure App Service built-in authentication and authorization support you can send the user id (unique user identification) inside the header.

[FunctionName("negotiate")]
    public static SignalRConnectionInfo GetSignalRInfo(
    [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
    [SignalRConnectionInfo(HubName = "tyton", UserId = "{headers.x-ms-signalr-userid}")] SignalRConnectionInfo connectionInfo,
    ILogger log)
    {
        // Provide your own authentication
        // If authenticated return connection informations
        return connectionInfo;
    }

Note:- The header is different x-ms-signalr-userid

And your client (I have used Typescript) should do an HTTP POST call to get the connection information from the negotiate function and then build the signalR hub connection with the connection information received from the negotiation HTTP call.

import axios from "axios";

const apiBaseUrl = `http://localhost:7071`;
let connection: signalR.HubConnection;

function connect(userId: string) {
    // Negotiation call
    getConnectionInfo(userId).then(
    info => {
        // make compatible with old and new SignalRConnectionInfo
        info.accessToken = info.accessToken || info.accessKey;
        info.url = info.url || info.endpoint;
        const options = {
            accessTokenFactory: () => info.accessToken
        };

        // Create hub connection
        connection = new signalR.HubConnectionBuilder()
            .withUrl(info.url, options)
            .configureLogging(signalR.LogLevel.Information)
            .configureLogging(signalR.LogLevel.Error)
            .build();

        connection.on('newMessage', newMessage);
        connection.on('newConnection', newConnection);
        connection.onclose(() => console.log('disconnected'));
        console.log('connecting...');
        console.log(userId);

        connection.start()
            .then((data: any) => {
                console.log("connected");
            })
            .catch((err: any) => {
                console.log("Error");
            });
         })
         .catch(err => console.log(err));
}

function getConnectionInfo(userId: string) {
    return axios.post(`${apiBaseUrl}/api/negotiate`, null, getAxiosConfig(userId))
     .then(resp => resp.data);
}

function getAxiosConfig(userId: string) {
    const config = {
      headers: { 'x-ms-signalr-userid': userId }
    };
    return config;
}

function newMessage(message: any) {
  console.log(message);
}

function newConnection(message: any) {
  console.log(message.ConnectionId);
}

And you can find more information Github thread and Js client

like image 141
Tharindu Diluksha Avatar answered Oct 20 '22 15:10

Tharindu Diluksha


With Azure SignalR Service, that mapping is taken care of by the service itself. You would just need to use the userId itself (which could map to multiple connections, one per connected client) and/or groupName as applicable.

The userId itself can be any string like a username or email ID. And the same goes for 'groupName'.

As an example, to work with groups, you would essentially need 3 functions like this

// Will be called by users themselves to connect to Azure SignalR directly.
//
// Example assumes Easy Auth is used
[FunctionName("Negotiate")]
public static SignalRConnectionInfo Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "negotiate")] HttpRequest req,
    [SignalRConnectionInfo(HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]SignalRConnectionInfo connectionInfo,
    ILogger log)
{
  return connectionInfo;
}

// Used by a group admin or the user themselves to join a group
//
// Example assumes both groupName and userId are passed
[FunctionName("AddToGroup")]
public static Task AddToGroup(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "{groupName}/add/{userId}")]HttpRequest req,
string groupName,
string userId,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRGroupAction> signalRGroupActions)
{
  return signalRGroupActions.AddAsync(
      new SignalRGroupAction
      {
        UserId = userId,
        GroupName = groupName,
        Action = GroupAction.Add
      });
}

// Used to send messages to a group. This would involve all users/devices
// added to the group using the previous function
//
// Example assumes both groupName and userId are passed
[FunctionName("SendMessageToGroup")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "{groupName}/send")]object message,
string groupName,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
  return signalRMessages.AddAsync(
      new SignalRMessage
      {
        // the message will be sent to the group with this name
        GroupName = groupName,
        Target = "newMessage",
        Arguments = new[] { message }
      });
}

You can read more about the Azure SignalR Service Bindings for Azure Functions in the official docs.

If you would still want to keep track of connections, you can subscribe to events that the service publishes which return the connectionId along with the userId of the client who connected/disconnected from the service.

The SignalRMessage and SignalRGroupAction classes support both connectionId and userId to identify connections and users respectively.

like image 44
PramodValavala-MSFT Avatar answered Oct 20 '22 15:10

PramodValavala-MSFT