javascriptazurewebsocketazure-functionsazure-web-pubsub

Azure Functions and webPubSub : get the number of clients


I'm trying to setup a simple chat app that uses WebSockets in a serverless way. I'm using Azure's WebPubSub service and the code is hosted in an Azure Function App.

I'm using javascript and basing my code in this sample app: https://github.com/Azure/azure-webpubsub/tree/main/samples/functions/js/simplechat That link has all the backend code and all the trigger and bindings are the same as mine.

I can run code on the following events :

for the connect event I have the following code: (connect/index.js)

  context.bindings.actions.push({
    "actionName": "addUserToGroup",
    "userId": `${context.bindingData.connectionContext.userId}`,
    "group": "group1"
  });

that basically tells webPubSub to add the user to a group, in this case called "group1".

For my application, when I send a message, from the client to the socket, I need to know if I'm the only one connected to that group. So far I didn't find anything in the docs regarding this.

In the sample app, whenever there's a message the following code runs (message/index.js)

var response = { 
....
    "states": {
      "counterState": {
        counter: msgCounter, 
      }
    }
  };
  return response;

they increment a msgCounter variable whenever there's a message and save it to the connection state I tried incrementing a variable when the connected event fires but is seems that I cannot set the state in that specific function. I then tried to do that on the message event but it seems that the state is not global for all the connections (i.e. it is specific to a unique connection)

Any help on this would be much apreciated. Thank you


Solution

  • There is no way to check if I'm the only one connected to that group. There is an API to check if there is any connection in the group using groupExists.

    But even with this API, the check and the following actions are not atomic, so even if groupExists returns no does not guarantee you are the first connected peer. Another challenge is that the connections can drop (for example due to network issues), so even if connection A is the first connected peer, it is possible that when connection B connects, connection A drops, leading connection B to believe that it is the first connected peer at that moment.

    For your scenario "The first connected peer has to wait for offers to connect to. the others send offers", this peering logic sounds better to be maintained by the application side using some global storage, for example, in Azure Table Storage, there is one method createEntity that only succeeds when the entity does not exist. The following code getOrAddFirstPeer shows how to set the first peer only when it does not exist and how to get the first peer when it exists using TableClient:

    const { AzureNamedKeyCredential, TableClient } = require("@azure/data-tables")
    const sharedKeyCredential = new AzureNamedKeyCredential("xxx", "xxx");
    const table = new TableClient("https://xxx.table.core.windows.net", "xxx", sharedKeyCredential);
    
    async function getOrAddFirstPeer(group, firstPeer) {
      var current = await getFirstPeerIfExists(group);
    
      if (current) return current;
    
      try {
        await table.createEntity({ partitionKey: "p1", rowKey: group, firstPeer: firstPeer });
        return firstPeer;
      } catch (error) {
        // first peer for the group already exists
        if (error.statusCode === 409) {
          return getFirstPeerIfExists(group);
        }
        throw error;
      }
    }
    
    async function getFirstPeerIfExists(group) {
      try {
        // store the first peer value in the firstPeer column
        const result = await table.getEntity("p1", group, { select: ["firstPeer"] });
        if (result) return result["firstPeer"];
        return null;
      } catch (error) {
        // first peer for the group does not exist
        if (error.statusCode === 404) {
          return null;
        }
        throw error;
      }
    }