Using Node.js + SockJS + Redis. How to associate socket id with redis subscribed channel for private chat and system notification?

  node.js, php, redis, sockjs, websocket

I was under the impression that the pub/sub feature in Redis associates a specific client (browser) connection with a channel that this client (browser) has subscribed to. So whenever I publish a message to that channel, only the client subscribed to that channel will receive it automatically without me having to write any logic to find which socket connection to write to.

This is what I did so far in my SockJS server:

var sockjs  = require('sockjs');
var http    = require('http');
var rds   = require('redis');

var redis_pub = rds.createClient(); //publisher
var redis_sub = rds.createClient(); //subscriber
var redis = rds.createClient(); // For doing other operations
var userid;
var user_data;

// Sockjs server

var sock = sockjs.createServer();
sock.on('connection', function(conn) {
    conn.authenticated = false;
   
    redis_sub.on("message", function(channel, message){
        console.log('Writing to channel '+channel+' the message '+message);
        conn.write(message);
    });

    redis_sub.on("error", function(error) {
        console.error(error);
      });
    
    conn.on('data', function(message) {
        message = JSON.parse(message);
        var req = {type:message.type,body:message.body};        
       
        if(req.type != 'auth' && !conn.authenticated)
        {
            return;
        }

        if(req.type=='auth')
        {
            // Authenticate user
            userid = req.body.userid;
            redis.hget('sessions', `user:${userid}`, function(error, result){                
                if(error != null){
                    console.log("error : "+error);
                }
                result = JSON.parse(result);
                user_data = result; 
                if(user_data.hash != null && user_data.hash == req.body.hash)
                {
                    conn.authenticated = true;
                    
                    // Tracking connection ids and store them in Redis to be used by PHP and for other operations.
                    if(user_data.hasOwnProperty('connids'))
                    {
                        user_data.connids.push(conn.id);
                    }
                    else
                    {
                        user_data.connids = [conn.id];
                    }
                    
                    redis.hset('sessions', `user:${userid}`, JSON.stringify(user_data));
                    console.log('Successful auth');                    
                    channel = 'channel-'+parseInt(userid); 
                    redis_sub.subscribe(channel);   //subscribe to a channel created only for this user
                    conn.write('auth,ok,subscribed to channel:'+channel);                   
                   
                }
                else
                {
                    console.log('Unsuccessfull auth with token '+req.body.hash);
                    console.log("session does not exist");
                    conn.write('auth,error,sessionnotfound');
                }
            });
        }
        else if(req.type =='chat')      /* ----- not finished yet, just testing..---- */
        {
            // Parse user message
            console.log('Publish message '+message[3]);
            // console.log('Wrinting to channel-'+message[1]);
            // console.log('Wrinting to channel-'+message[2]);
            message.shift();
            var msg = message.join(',');
            console.log('Publishing message '+msg+' to channels '+message[0]+','+message[1]);
            redis_pub.publish('channel-43', msg);
        }
    });

    conn.on('close', function(){
        conn.authenticated = false;
        conn.write('Connection closed.');

        /* --- I'm trying here to manually keep track of live/dead connections and updating stored conn_ids in Redis */
        if(user_data.hasOwnProperty('connids')) 
        {
            // remove connection id from connids stack for user
            var index = user_data.connids.indexOf(conn.id);
            if (index > -1) {
                user_data.connids.splice(index, 1);
                redis.hset('sessions', `user:${userid}`, JSON.stringify(user_data));
            }
        }
        
       
    });
});

var server = http.createServer();

sock.installHandlers(server);

console.log(' [*] Listening on 127.0.0.1:3000' );
server.listen(3000, '127.0.0.1');


Basically, after successful HTTP login handled by backed (PHP), I store some info regarding this session in Redis among which is a token to be verified later in the above server for auth.

User browser sends a JSON message including username and the token generated above to be validated in server.js If valid user is authenticated and ready to send/receive messages.

Now, when I try to publish a message with the channel that the user has subscribed to before in server.js The messages delivered to everyone else connected to the server!

Am I supposed to handle the connection-channel association on my own?

So, what is the channel that I directed my message to is good for?! Maybe I can just embed some info inside my published message that will help me identify the specific user connection that the message is directed to without using channels or pub/sub at all. Right?

Does that mean that the pub/sub is not meant to do that and it’s only for scalability when running more than one instance of the server?

Am I missing a lot here?
How the whole system should work for a private one-on-one chat and for serving real time system notification?

Source: Ask PHP

LEAVE A COMMENT