'Socket.io and Redis Adapter same rooms and different servers

We are creating a scalable real-time collaborative text editor. The client (frontend) code is written in React.js, and the server (backend) code is Websockets (socket.io).

The client creates a connection with the websocket server and joins a room based on the documentID opened in the URL, so that each event is done inside the same document will be shared across all clients opening the same document since they are joining a room with the same documentID

client code

useEffect(() => {
    if (socket == null || quill == null) return

   //Emit save-document event every 500ms
    const interval = setInterval(() => {
      socket.emit("save-document", quill.getContents())
    }, SAVE_INTERVAL_MS)

    return () => {
      clearInterval(interval)
    }
  }, [socket, quill])

  useEffect(() => {
    if (socket == null || quill == null) return

    socket.once("load-document", document => {
      quill.setContents(document)
      quill.enable()
    })
    socket.emit("get-document", documentId)
  }, [socket, quill, documentId])

 useEffect(() => {
    if (socket == null || quill == null) return

    const handler = delta => {
      quill.updateContents(delta)
    }
    socket.on("receive-changes", handler)

    return () => {
      socket.off("receive-changes", handler)
    }
  }, [socket, quill])

server code

socket.on('get-document', async (documentID) => {
        const document = await lookUpDocument(documentID);
        socket.join(documentID);
        socket.emit("load-document", document.data);
        socket.on("send-changes", (delta) => {
            socket.broadcast.to(documentID).emit("receive-changes", delta)
        })

        socket.on("save-document", async (data) => {
            await Document.findByIdAndUpdate(documentID, { data })

        })

It works perfectly if we are working on the same server, but we want to have several Websockets servers that are connected communicate with each other as well. For example

Client1 connected to WS1.

Client2 and Client3 connected to WS2.

The three Clients are opening the same document, so that each event emitted by a client must be broadcasted to all the other clients even if they are connected to different Websockets. It should look like this Clients and different websocket servers

After searching I found out that we should consider Publish/Subscribe Architecture so that servers can subscribe and publish events. We also found that Socket.io has Redis-Adapter which does the same thing. So we modified our server code be like this

const io = new Server(process.env.PORT, {
    cors: {
        origin: process.env.CLIENT_URL,
        methods: ["GET", "POST"]
    }
})

const pubClient = createClient({ host:'https://<ngrokurl>.eu.ngrok.io'});
const subClient = pubClient.duplicate();
pubClient.on('ready', () => {
    console.log('Publisher connected to redis and ready to use')
})
subClient.on('ready', () => {
    console.log('Subscriber connected to redis and ready to use')
})
pubClient.on('error', (err) => console.log('Publisher Client Error', err));
subClient.on('error', (err) => console.log('Subscriber Client Error', err));

Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
    //Connecting the socket server to the redis channel
    //using Socket.io Redis-Adapter
    io.adapter(createAdapter(pubClient, subClient));
});

const defaultValue = ""
var allClients = [];

io.on("connection", (socket) => {
    allClients.push(socket)
    var username = socket.handshake.query.username
    console.log(`A client is connected! ${username} - Number of sockets is: ${allClients.length}`)

    //Event listener for client's socket disconnect
    //Event that listens to any
    socket.on('disconnect', function (reason) {
        console.log(`${username} got disconnected due to ${reason}`)
        var i = allClients.indexOf(socket);
        allClients.splice(i, 1);
        console.log(`Number of sockets now is: ${allClients.length}`)
    })


    socket.on('get-document', async (documentID) => {
        const document = await lookUpDocument(documentID);
        socket.join(documentID);
        socket.emit("load-document", document.data);
        socket.on("send-changes", (delta) => {
            socket.broadcast.to(documentID).emit("receive-changes", delta)
        })

        socket.on("save-document", async (data) => {
            await Document.findByIdAndUpdate(documentID, { data })

        })

    })
})

Yet each server now doesn't broadcast the events to all the websocket servers whenever the document changes? How can I make each socket subscribe and publish events to all the clients that are joining the same room (based on the documentID)?

Thank you so much in advance

Edit 1: Added the redis url that we connect to which is an ngrok server that tunnels to a localhost:6379 on a virtual machine.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source