Socket.io doesn't remove user object from array after disconnect - javascript

const socketUsers = [];
io.on("connection", (socket) => {
socket.on("user", (userdata) => {
const aUser = { socket: socket, user: userdata };
socketUsers.push(aUser);
});
socket.on("disconnect", () => {
delete socketUsers[socket.id];
console.log(socketUsers);
});
});
The socket and user field is also assigned to the aUser then it's pushed into the socketUsers array. This happens after the user logs in because we need their userdata.
When the user disconnects, the console logs the array for me but the user is still there (both socket and userdata fields).
What am I doing wrong?

You are indexing the array wrong, pushing new sockets onto it won't make those sockets' ids be indexes of the array, from what i can tell you probably should use an object instead.
const socketUsers = {};
io.on("connection", (socket) => {
socket.on("user", (userdata) => {
const aUser = { socket: socket, user: userdata };
socketUsers[socket.id] = aUser;
});
socket.on("disconnect", () => {
delete socketUsers[socket.id];
console.log(socketUsers);
});
});
If you are so insistent on using an array, you would have to manually search it for index of the element you're looking for
const socketUsers = [];
io.on("connection", (socket) => {
socket.on("user", (userdata) => {
const aUser = { socket: socket, user: userdata };
socketUsers.push(aUser);
});
socket.on("disconnect", () => {
socketUsers.splice(socketUsers.findIndex(elem => elem.socket.id === socket.id), 1);
console.log(socketUsers);
});
});

Related

how to get socket.on("test", { count)) current value when component mounted?

import ...
let socket;
const Game = () => {
const ...
const [userCount, setUserCount] = useState(0);
const scrollToBottom = () => {
endToMessages.current?.scrollIntoView({ behavior: "smooth" });
};
const { _id, username } = JSON.parse(localStorage.getItem("user")).user;
useEffect(() => {
socket = io(process.env.REACT_APP_BASE_URL);
socket.emit("game_lobby", { id: _id, username, room: gameInfo.name });
return () => socket.disconnect();
}, [_id, username, gameInfo.name]);
useEffect(() => {
socket.on("message", ({ user, text }) => {
setMessages((prev) => [...prev, { user, text }]);
scrollToBottom();
});
socket.on("user_count",({count}) => {
setUserCount(count);
})
}, []);
console.log("Current user count",userCount);
const sendMessage = () => {
...
};
return (
<div className="game section">
...
</div>
);
};
export default Game;
Server side:
import { Server } from "socket.io";
const socketApi = (server) => {
const io = new Server(server, {
cors: {
origin: ["https://mook-f2b4e.web.app", "http://localhost:3000"],
methods: ["GET", "POST"],
},
});
io.on("connection", (socket) => {
socket.on("disconnect", () => {
console.log(`user ${socket.id} had left`);
});
socket.on("game_lobby", async ({ id, username, room }) => {
console.log("We have a new connetion.");
socket.join(room);
const roomClients = await (await io.in(room).fetchSockets()).length;
console.log(roomClients);
io.to(room).emit("user_count", { count: roomClients });
socket.on("disconnect", () => {
io.to(room).emit("user_count", { count: roomClients });
});
socket.emit("message", {
user: "Admin",
text: `${username} welcome to ${room} room`,
});
});
socket.on("send_message", ({ name, message, room }) => {
io.to(room).emit("message", { user: name, text: message });
});
});
return io;
};
export default socketApi;
Hi all.When I try to get user count when component mount but I can't.First time when component did mount I get 0 value. If someone else joins the room then I can get its current value. If I could explain properly I mean let's say there are 3 people in the room and I joined that room later. Now there are 4 people in the room but I got 0 value from console.log("Current user count", userCount) if the 5th person joins the room then I can get the current value from the server.
I believe you would need the server to have a variable to keep track of active users. So connection increment a variable for onlineUsers, then on disconnect subtract one from the onlineUsers
See reference here => https://stackoverflow.com/a/10276446/3124019

Node server keep shutting down when I start typing message

I am making a chat app, but when I write a message and press send, my node server keep giving me:
TypeError: Cannot read property 'trim' of undefined
or
TypeError: Cannot read property 'room' of undefined
Then it crashes, I don't know why, everything else was working normally, only the server crashes. I've tried fixing it myself to no avail, without the server, you can probably guess what will happen.
Here's the code:
index.js:
var express = require ('express')
var http = require ('http')
var { addUser, removeUser, getUser, getUsersInRoom } = require ('./user')
var router = require ('./router')
var { callbackify } = require('util')
var PORT = process.env.PORT || 5000
var app = express()
var server = http.createServer(app)
var io = require('socket.io')(server, {
cors: {
origin: '*',
}
});
app.use(router)
io.on('connection', (socket) => {
socket.on('join', ({ name, room }, callback) =>{
var { error, user } = addUser({ id: socket.id, name, room })
if(error) return callback(error)
socket.emit('message', { user: 'admin', text: `Now, I- Oh, ${user.name}, welcome to room ${user.room},enjoy your time here!` })
socket.broadcast.to(user.room).emit('message',{user: 'admin', text:`Hey, just coming in to say ${user.name} joined, also, how you guys doin.`})
socket.join(user.room)
callback()
})
socket.on('sendMessage', (message, callback) => {
var user = getUser(socket.id)
io.to(user.room).emit('message', { user: user.name, text: message })
callback()
})
socket.on('disconnect', () => {
console.log('Aw they left :(');
});
});
server.listen(PORT, () => console.log (`This is Index, port ${PORT} secured, lots of unfixable bug`))
user.js(where the problem exist):
const users = []
const addUser = ({ id, name, room }) =>{
name = name.trim().toLowerCase()
room = room.trim().toLowerCase()
const existingUser = users.find((user) => user.room === room && user.name === name)
if(existingUser){
return {error: 'Username is already taken you dumbass'}
}
const user = {id, name, room}
users.push(user);
return{ user }
}
const removeUser = (id) =>{
const index = users.findIndex ((user) => user.id === id)
if(index !== -1) {
return users.splice(index, 1)[0]
}
}
const getUser = (id) => users.find((user) => user.id === id)
const getUsersInRoom = (room) => users.filter((user) => user.room === room)
module.exports = { addUser, removeUser, getUser, getUsersInRoom }
and router.js:
const express = require('express')
const router = express.Router();
router.get('/*', (req,res) =>{
res.send(`Server is up and running`)
})
module.exports = router;
That is all the server file because the problem comes from server, not client (there is not enough words in here so I have to do this)
Thanks in advance!
Both error is due to the application is trying to access a subfunction/subparameter of an undefined value.
Are you sure the client is sending the right param when emitting "join" ?
what you can do on the server side is do some validation and check if its undefined and set a default value if it is.
name = name ? name.trim().toLowerCase() : "default_name";
room = room ? room.trim().toLowerCase() : "default_room";
as for the Room of undefined error is most likely due to this part of the app
const existingUser = users.find((user) => user.room === room && user.name === name)
try checking if users.length, if it's 0 then don't bother doing .find(), or you can add validation inside the .find() function to check if user is undefined,then return false;
const existingUser = users.find((user) => user && user.room === room && user.name === name)
socket.on('join', ({ name, room }, callback) =>{
Where do you get name and room from?

Can anyone please tell me what's wrong this socket event?

I've emitted two events on user joined & left (user_joined and user_left). It's working on the server-side but not working on the client-side.
Server-side code: (it's working, showing console.log on every connection)
io.on('connection', function (socket) {
const id = socket.id;
/**
* User Join Function
*/
socket.on('join', function ({ name, room }) {
const { user } = addUser({id, name, room}); // add user to users array
socket.join(user.room);
socket.emit('user_joined', users); // emit event with modified users array
console.log(id, 'joined')
})
/**
* User Disconnect function
*/
socket.on('disconnect', () => {
removeUser(id); // remove user form users array
socket.emit('user_left', users); // emit event with modified users array
console.log(id, 'left')
})
})
Client-side code: (Not firing on user_joined or user_left)
const [players, setPlayers] = useState([]);
const ENDPOINT = 'localhost:5000';
socket = io(ENDPOINT);
useEffect(() => {
const name = faker.name.firstName() + ' ' + faker.name.lastName();
socket.emit('join', {name, room: 'global'}); // it's working fine
return () => {
socket.emit('disconnect');
socket.off();
}
}, [])
useEffect(() => {
socket.on('user_joined', (users) => {
setPlayers(users);
}); // >>> Not Working <<<
socket.on('user_left', (users) => {
setPlayers(users);
}); // >>> Not Working <<<
console.log(socket) // it's working fine
}, [players]);
The socket instance needs to be created only once. In your case, it is getting created on every re-render. Also you do not need 2 useEffects.
Put the creation of socket instance and merge your 2 useEffects into 1 and provide an empty array as dependency. With this, your useEffect is executed only once and not on every re-render.
Try this
const [players, setPlayers] = useState([]);
useEffect(() => {
const ENDPOINT = 'localhost:5000';
socket = io(ENDPOINT);
const name = faker.name.firstName() + ' ' + faker.name.lastName();
socket.emit('join', {name, room: 'global'});
socket.on('user_joined', (users) => {
setPlayers(users);
});
socket.on('user_left', (users) => {
setPlayers(users);
});
console.log(socket);
return () => {
socket.emit('disconnect');
socket.off();
}
}, []);
...
If you want to use the socket instance in other places of your component then make use of useRef. With useRef, you always get the same instance unless you mutate it.
create socket with refs
...
const [players, setPlayers] = useState([]);
const ENDPOINT = 'localhost:5000';
const socketInstance = useRef(io(ENDPOINT));// in react, with useRef, you always get the same instance unless you mutate it.
useEffect(() => {
// socketInstance.current = io(ENDPOINT);
const name = faker.name.firstName() + ' ' + faker.name.lastName();
socketInstance.current.emit('join', {name, room: 'global'});
socketInstance.current.on('user_joined', (users) => {
setPlayers(users);
});
socketInstance.current.on('user_left', (users) => {
setPlayers(users);
});
console.log(socketInstance.current);
return () => {
socketInstance.current.emit('disconnect');
socketInstance.current.off();
}
}, []);
...

How to test a function with Jest that processes data from an external variable

I want to test a function that returns a user by ID from a list of users!!
There is a file responsible for working with the list of users:
const users = [];
const getUser = (id) => users.find((user) => user.id == id);
module.exports = { users, addUser, removeUser, getUser, getUsers };
Unfortunately, I did not find a solution on how to test this function. Expected result is undefined, because the users array is empty. I do not understand how I can replace an array of users for testing.
const { getUser } = require('../users');
describe('Socket', () => {
let socketId;
beforeEach(() => {
socketId = 'qwertyqwerty';
})
test('getUser', () => {
const user = getUser(socketId);
expect(user).toEqual({id: 'qwertyqwerty',user:{username: 'Max'}});
});
})
Conjured a decision!!! in short. used a babel-plugin-rewire. And here's how to implemen:
users.js
import Helper from './Helper';
const users = [];
const user = {
getUser: (id) => users.find((user) => user.id == id),
}
module.exports = user;
And test file:
const User = require('../users');
User.__Rewire__('users', [{id:'qwertyqwerty',user:{username: 'Max'}},{id:'asdfghasdfgh',user:{username: 'Andy'}}]);
describe('Socket', () => {
let socketId;
beforeEach(() => {
socketId = 'qwertyqwerty';
})
test('getUser is user error', () => {
const user = User.getUser(socketId);
expect(user).toEqual({id: 'qwertyqwerty',user:{username: 'Max'}});
});
})
Thanks to Always Learning, for the quick and correct answer )))

in socket.on callback get name of listener in the socket object

Where can i find the socket listener name in the socket object ?
Example :
socket.on('test', function(data) {
// is there a property of socket object that is equal to 'test' ?
})
EDIT: I want this because my listeners and my callbacks are in different files and I don't want to copy the listener name by hands.
socketController with listeners :
'use strict';
const onSendMessage = require('./message/send');
const onGetMessages = require('./message/get');
const onAskFriend = require('./friends/ask');
const onAcceptFriend = require('./friends/accept');
const onRemoveFriend = require('./friends/remove');
const onGetFriends = require('./friends/get');
module.exports = (socket) => {
// message
listenTo(socket, 'sendMessage', onSendMessage);
listenTo(socket, 'getMessages', onGetMessages);
// friends
listenTo(socket, 'getFriends', onGetFriends);
listenTo(socket, 'askFriend', onAskFriend);
listenTo(socket, 'acceptFriend', onAcceptFriend);
listenTo(socket, 'removeFriend', onRemoveFriend);
};
function listenTo(socket, name, callback) {
socket.on(name, data => callback(socket, data));
}
socket getFriends :
'use strict';
const db = require('../../database/mongo').collections;
const ObjectID = require('mongodb').ObjectID;
module.exports = (socket) => {
const query = {
ownerid: ObjectID(socket.id)
}
db.friends.find(query).toArray((err, friends) => {
if (err) {
return socket.emit('getFriends', {status: 500});
}
socket.emit('getFriends', {status: 200, body: friends});
});
};
I don't like writing socket.emit('getFriends') by hands each time, it can be error prone.
I don't knwow if you can do this (I think no) but why not simply:
function listenTo(socket, name, callback) {
socket.on(name, data => callback(socket, name, data));
}
and :
module.exports = (socket, message) => {
const query = {
ownerid: ObjectID(socket.id)
}
db.friends.find(query).toArray((err, friends) => {
if (err) {
return socket.emit(message, {status: 500});
}
socket.emit(message, {status: 200, body: friends});
});
};

Categories