signalrsignalr-hubazure-signalrdapr

How to use Dapr SignalR Output Binding?


It's not clear to me how to use Dapr's SignalR output Binding.

If I understand correctly, SignalR works with communication between three parties:

I also understood that for a client to connect to the hub, it must first connect to the server, which will do the redirection.

Since Dapr connects directly to the Hub, I assume it is the server, right?

And if so, how do I connect a client to it?


Solution

  • @ken-chen provided a great answer on github:

    In order to SignalR service, client needs to first do a negotiate with server to get the url of the service and access token. Dapr SignalR binding doesn't have a built-in support for this, so you need to implement it by yourself. You can refer to this doc about how to expose a negotiate endpoint manually: https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-reference-data-plane-rest-api#implement-negotiate-endpoint

    Also seems Dapr SignalR binding only supports output binding so you can only use it send messages to clients but cannot receive messages from client. You can do it by yourself by implement webhook to receive messages. Checkout this doc for details: https://learn.microsoft.com/en-us/azure/azure-signalr/concept-upstream

    Here is a code snippet that shows how to do the negotiate and send message using Dapr binding (using node.js and express). Hope it helps.

    import { DaprClient } from '@dapr/dapr';
    import express from 'express';
    import jwt from 'jsonwebtoken';
    
    const client = new DaprClient(process.env.DAPR_HOST || 'http://localhost', process.env.DAPR_HTTP_PORT || '3500');
    const hub = 'chat';
    const connectionString = '<your-connection-string>';
    let app = express();
    app.get('/send', (req, res) => {
      client.binding.send('signalr', 'create', {
        Target: 'foo',
        Arguments: ['bar']
      });
      res.send('done');
    }).post('/chat/negotiate', (req, res) => {
      let endpoint = /Endpoint=(.*?);/.exec(connectionString)[1];
      let accessKey = /AccessKey=(.*?);/.exec(connectionString)[1];
      let url = `${endpoint}/client/?hub=${hub}`;
      let token = jwt.sign({ aud: url }, accessKey, { expiresIn: 3600 });
      res.json({ url: url, accessToken: token });
    });
    app.listen(5000, () => console.log('server started'));
    

    Then call /send the send the message and use the following code in browser to receive it:

    let connection = new signalR.HubConnectionBuilder().withUrl('/chat').build();
    connection.on('foo', message => console.log(message));
    connection.start();
    

    In order to get the user id on the server, that same user id that goes in the "user" field of "metadata", he answered:

    User ID can be set in the sub claim when generating the token:

    let token = jwt.sign({ aud: url, sub: '<user-id>' }, accessKey, { expiresIn: 3600 });
    res.json({ url: url, accessToken: token });