'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

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 |
|---|
