Search code examples
socket.iocallback

emit a message from socket.io server to a specific socket.id and receive a callback


I have multiple IOT devices with socket.io clients connecting to a socket.io server. I would like a client to be able to emit a file to be backed up to another client and receive a response that it was done. Since the message is from client to client, it has to be relayed through the server and since socket.IO does not support callbacks on broadcasts and the only way I know how to send an emit message to a specific socket.id is io.to(socket.id).emit(), which is handled as a broadcast, I'm not sure how to proceed. Here is my code:

socket.io Server:

let connections = []

io.on("connection", (socket) => {
    socket.on("disconnect", () => {
        connections = connections.filter(item => item.socketId !== socket.id)
    })

    socket.on('setConnectionStatus', (device) => {
        connections.push({device: device, socketId: socket.id})
    })


    //relay backup messages
    socket.on('Backup', (file, callback) => {
        console.log(file)
        let systemController = connections.findIndex(x => x.device == 'system_controller' )
        systemController = connections[systemController].socketId
        io.to(systemController).emit('Backup', file, (status) => {
            console.log(status)
            callback(status)
        })
    })

})

sending socket.io client:

const socketServer = "http://10.10.10.1:3001"
const deviceName = 'liveNet'

const socket = io(socketServer)
socket.on("connect", () => {
    socket.emit("setConnectionStatus", deviceName)
})


function backupToSystemController() {
    return new Promise(function(resolve, _) {
        const file = fs.readFileSync(__dirname + '/public/config/config.json') 
        socket.emit('Backup', file, (status) => {
        resolve(status)
        })
    })
    
}

//this is called in an api
backupToSystemController() 

receiving socket.io client:

const socketServer = "http://10.10.10.1:3001"
const deviceName = 'system_controller'

const socket = io(socketServer)
socket.on("connect", () => {
    socket.emit("setConnectionStatus", deviceName)
})

socket.on('Backup', (file, callback) => { 
    let response 
    fs.writeFile(__dirname + '/backups/config.json', file, (err) => { 
        if (err) { 
            response = {message: 'Failure' + err} 
        } 
        else { 
            response = {message: 'success'} 
        } 
        callback(response) 
    }) 
})
    

Everything works except the callback listener on the server never gets a response because it and I get a timeOut error:

Error: operation has timed out
    at Timeout._onTimeout (/home/pi/aeiProject/socketIO/server/node_modules/socket.io/dist/broadcast-operator.js:182:17)
    at listOnTimeout (node:internal/timers:569:17)
    at process.processTimers (node:internal/timers:512:7)

Solution

  • I was able to fix the issue by pushing the actual socket into the object I added to the connections array, and then referencing that socket when sending the emit. See updated socket.io Server code below.

    let connections = []
    
    io.on("connection", (socket) => {
      socket.on("disconnect", () => {
        connections = connections.filter(item => item.socketId !== socket.id)
    })
    
    socket.on('setConnectionStatus', (device) => {
        connections.push({device: device, socketId: socket.id, socket: socket})
    })
    
    
    //relay backup messages
    socket.on('aeinetBackup', (file, callback) => {
        let system_controller = connections.findIndex(x => x.device == 'system_controller' )
        let systemControllerSocket = connections[system_controller].socket
        systemControllerSocket.emit('aeinetBackup', file, (status) => { // in order to get a callback we have to use the specific socket for the system controller. Otherwise if we didn't need a callback we could use io.to(socket.id).emit()
            callback(status)
        })
    })
    

    })