fastify/help

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.