Nat
Nat

Reputation: 1782

Is it possible to do a server-side callback within Socket.IO

There was this question (and comment) that asks my exact question, but it was never answered decently. I cannot find anywhere else that could help me this this really annoying issue.

I have this product that sits at a users home connected to their network. When they're outside their network, I'd like them to be able to access said product anywhere in the world with an internet connection securely. When my app needs data (that is only stored on the product at home) it goes through these steps:

  1. App sends request to server that it wants data from their product sitting at home
  2. Server get's request and sends a message down to their product via socket.io
  3. Product receives request and fetches the data (that's stored locally) and returns it back to the server (the callback)
  4. Server receives the data sent by the callback and sends it back to the users App.

Now I know with socket.io you can do callbacks client side, but why not server side callbacks?

Currently, with the code I have now I get the error Callbacks are not supported when broadcasting:

Server-side code (Nodejs):

app.get('/hueapi/lights', verifyToken, (req,res) => {
  const bridgeIDFromApp = req.header('bridgeID');
  const socketID = socketRefDic[bridgeIDFromApp]['socketID'];
  io.to(socketID).emit('getAllLights', function(data){
      res.send(data); // The callback function that shows the data given by the python client
  });
});

Client-side code (python):

def getAllLights(data):
    lightData = requests.get('http://localhost:4000/lights/')
    return lightData

Is there a way/method around this, I find it silly how server-side callbacks aren't supported, or am I just doing something wrong?

The thing is, multiple different products will be connecting at once to this server, so a callback would be the only logical way to send the data to the right place. I've seen solutions of people making their own callbacks but I'd need it so multiple instances of this code can be executed and sent all in one go rather than break it up in different emit functions.

Upvotes: 2

Views: 464

Answers (1)

Nat
Nat

Reputation: 1782

I have found a small and effective solution to server-side calling with Socket.io after hours of smashing my head against my desk!

I will make this as detailed as possible as I've seen all over the internet the same question...

So for those who (like me) will have multiple clients connecting to the same server, and you wanted to use callbacks to send data from the client back to the server in one, manageable way without the use of callbacks as we cannot use them. Or for those who want to have a callback feature when talking directly to specific sockets:

For example, my app makes a request to my server asking for data that is held elsewhere (in my case my app is asking for data about some lights that are held on a raspberry pi in their home, so they can access that data wherever they are).

My app calls this endpoint of my Cloud API (Nodejs):

app.get('/hueapi/lights', verifyToken, (req,res) => {
  const bridgeIDFromApp = req.header('bridgeID');
  const socketID = socketRefDic[bridgeIDFromApp]['socketID'];
  io.to(socketID).emit('getAllLights', 'getAllLights')
  lightDataCallbackFunction = function(data){
    console.log(Object.keys(data))
    if(Object.keys(data) == socketID){
      res.send(data);
    }
  }
});

And my python client (that sits at my users home) for this relevant function:

    @sio.event
    def getAllLights(data):
            lightData = requests.get('http://192.168.0.55:5000/lights').json()
            output = {sio.eio.sid: lightData};
            sio.emit('lightDataCallback',output)

Breaking down this code....(The two constants at the top bridgeIDFromApp and socketID are just two constants that I store on the server so the server knows where to send data)

  1. The app makes an API call and sends a message via socket to the client holding the data:

    io.to(socketID).emit('getAllLights', 'getAllLights')

  2. My python client receives the command, gets said data via a localIP, sticks it in a JSONObject attached with the socketID so we know where to send the data to when it goes back to the server and sends it back to the server:

    sio.emit('lightDataCallback',output)

  3. This is where it gets creative, as we cannot do callbacks, I make a listener within the function where the socket object is first used to connect the user. This is used as it will receive all emits from all clients going to the lightDataCallback emit function. So within the socket object function I have this code to retrieve said data:

    socket.on('lightDataCallback', (socket) => {
      lightDataCallbackFunction(socket);
    });
    

    Then we call the function that was originally instantiated within the first endpoint that we called by our app from this emitter:

    lightDataCallbackFunction = function(data){
    console.log(Object.keys(data))
    if(Object.keys(data) == socketID){
          res.send(data);
      }
    }
    

Now, we could just send the data back to the client with a simple res.send(data). However, if there are thousands of clients connected, and a lot of people running the same function then you're going to run into the issue of data being sent to the wrong person as there is no way to tell that the functions and data are returned in order (maybe their client raspberry pi at home is slow to retrieve the data).

To make sure it goes to the right person, I have a simple if statement to check if the data returned back to the function is actually for the person. It checks if the socketID in the data is the same as the current socketID doing the request, if not then just don't do anything until this function is called again and check again. If it is, then we can safely assume that the data returned to our function is going to the correct person, and so we can send the data:

if(Object.keys(data) == socketID){
  res.send(data);
}

(Inside my data returned to the server is the socketID, look at step 2. It is what we used to tag data with their owner).

Upvotes: 2

Related Questions