Websocket + redis, broadcast to all clients except sender?
shania-g opened this issue · 5 comments
I am using fastify/websocket
and fastify/redis
to handle realtime collaboration users, depending on what page they are currently working on. A redis channel with the page's UUIDv4 is created, and in that channel the updates are broadcasted to all the clients currently connected to it.
I only have one problem now, and that is that the sender should not receive a message when a broadcast happens, but all other clients should.
Unfortunately I am not entirely sure how I could modify my current code to handle that functionality?
This is what I currently have, and it works fine:
Plugin registration:
fastify.register(fastifyRedis, {
host: 'localhost',
password: 'local',
port: 6379,
namespace: 'sub',
closeClient: true
})
fastify.register(fastifyRedis, {
host: 'localhost',
password: 'local',
port: 6379,
namespace: 'pub',
closeClient: true
})
fastify.register(fastifyWebsocket, {
logLevel: 'debug'
})
Main logic:
async function getPages(socket: WebSocket, req: FastifyRequest) {
const { id } = req.params
const channelName = `page_${id}`
await req.server.redis.sub.subscribe(channelName, (err) => {
if (err) {
console.error(`Error subscribing to ${channelName}: ${err.message}`)
}
})
req.server.redis.sub.on('message', (channel, message) => {
if (channel === channelName) {
req.server.websocketServer.clients.forEach((client) => {
client.send(message)
})
}
})
}
Sending notification to trigger broadcast:
async function createPage(req: FastifyRequest, reply: FastifyReply) {
await req.server.redis.pub.publish(
`page_${pageId}`,
JSON.stringify({
data
})
)
}
It would be wonderful if someone could give me an example on how I could modify the code to exclude the sender/initiator of the broadcast event from also receiving the notification
You need to include a client identifier in the payload, and skip sending the message if equal. This can be a uuid or hyperid that you generate whenever a clients connects.
@mcollina did this now, but not sure if this is how I should do it. Works fine though:
Main logic:
async function getPages(socket: WebSocket, req: FastifyRequest) {
req.server.websocketServer.clients.forEach(
(client: WebSocket & { id?: string }) => {
if (!client.id) {
client.id = req.user?.id
}
}
)
req.server.redis.sub.on('message', (channel, message) => {
if (channel === channelName) {
req.server.websocketServer.clients.forEach(
(client: WebSocket & { id?: string }) => {
if (
client.readyState === 1 &&
client.id !== JSON.parse(message).senderId
) {
client.send(message)
}
}
)
}
})
}
Sending notification:
async function createPage(req: FastifyRequest, reply: FastifyReply) {
await req.server.redis.pub.publish(
`page_${pageId}`,
JSON.stringify({
data,
senderId: req.user?.id
})
)
}
That works. I would not use the user.id because it might be that a user has two windows open, but I guess it's a choice ;).
That works. I would not use the user.id because it might be that a user has two windows open, but I guess it's a choice ;).
Thank you for the confirmation 👍
I already figured that the user id is a problem, is there anything that Fastify offers in the Request
that I could use here to identify each connection in a unique way, even if it is the same user?
none out of the box that would work for your use case, create a fresh id.