socket.io: Avoiding server to client to sever communication - javascript

This may seem odd but I have a situation where I'm keeping an array of uses in io.on('connection', function(socket) { ... }); scope for each user currently connected to a room. So when a user disconnects, I need to update that array on the server. The issue is, since this array is unique to each socket/user I can't just update the array directly.
So what I'm doing is broadcasting an update to all users then immediately having users emit a message back. Is there a cleaner method of doing this?
Quick and dirty example:
// server.js
io.on('connection', function(socket) {
var usersArray = getUsersArray();
socket.on('user array update', function() {
usersArray = removeUser(user);
});
socket.on('disconnect', function() {
socket.broadcast.to(room).emit('user disconnected', user);
});
});
//client.js
socket.on('user disconnect', function(user) {
socket.emit('user array update', user);
});
I'm new to node.js but this ping pong type communication feels a bit unnecessary, is this pretty standard or is there a better way I should be doing this?

Related

Socket.io + express rooms

I am working on an online website with real-time chat and I've been trying to set up socket.io rooms for a few days now, but one of my custom events just doesn't emit.
My website has 2 paths, / and /play. When someone fills out a form in the / path, the website redirects them to the /play path.
const io = require('socket.io')(listener);
io.on('connection', socket => {
socket.on('add user', data => { // This event gets emitted when the user submits the form at `/`, in a client-side js file.
socket.username = data.username;
socket.room = data.room;
socket.score = 0;
socket.join(data.room);
io.sockets.in(data.room).emit('user joined', { // This event gets 'handled', in another (not the same one) client-size js file.
username: data.username,
room: data.room
});
});
socket.on('disconnect', function () {
io.sockets.in(socket.room).emit('user left', {
username: socket.username,
room: socket.room
});
socket.leave(socket.room)
});
});
In the other client-side js file:
var socket = io.connect();
let joined = 0;
socket.on('user joined', data => {
joined++;
console.log(joined, data)
const elemental = document.createElement("LI");
const info = document.createTextNode(data.username);
elemental.appendChild(info);
document.getElementById('people').appendChild(info)
});
The add user event code executes just fine, but the user joined one doesn't..
I'm almost certain that it has to do with the paths. I read on socket.io namespaces but it seems like they are just like a different type of rooms.
Can someone tell me what's wrong?
EDIT: I should mention that no errors come up.
Without seeing your whole code, I'm going to assume that the socket on client-side.js code, is not joined to the data.room room, and that's why you don't get anything on the 'user join' event listener and that happens because you probably have multiple socket.io connections on the client side code.
You have multiple ways to solve this, depending on what you're trying to achieve.
Instead of only emitting to people on the room, emit to all sockets that an user joined a specific room. io.emit('user joined') instead of io.sockets.in(data.room)...
There should only be one single var socket = io.connect(); on the front end, otherwise the socket that's emitting: add user and therefore joined to data.room is not the same socket that's listening on: user joined, and that's why you never get the event on that socket, because you have 2 different sockets, one that joined the room, and one that is doing absolutely nothing except waiting for an event that will never occur.

My private messaging doesn't work with socket.io

I can't understand why my code (below) doesn't work. The console.log prints the correct socket.id on the server-side, before emitting to the socket. So why isn't the message received?
server-side:
socket.on("connectToUser", function(userName, currentUserName, userID){
console.log("user wants to connect to: ",userID);
socket.to(userID).emit("connectNotification", currentUserName);
});
client-side:
socket.on("connectNotification", function(currentUserName){
console.log("correct user notified");
$("#connectToBox").append("Hallo");
});
Does the socket.id have to be the original one? I am changing it on connection, like this:
socket.on('connection', function(socket){
console.log('a user connected', socket.id);
var id = uuid.v4();
socket.id = id;
console.log(socket.id);
Changing socket.id after the connection event has fired is probably too late, because at that point, socket.io has already done some internal housekeeping based on the original id.
If you want to use your own id's, you should use a middleware function instead (disclaimer: I'm not overly familiar with the internals of socket.io, but some example code I put together seems to work):
let server = require('socket.io')(...);
server.use(function(socket, next) {
socket.id = uuid.v4();
next();
}).on('connection', function(socket) {
socket.on("connectToUser", function(userName, currentUserName, userID){
console.log("user wants to connect to: ",userID);
socket.to(userID).emit("connectNotification", currentUserName);
});
});

return client socket.io to variable

On the docs page they say that i need to use like this.
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
there is any way to expose the client to a variable?
var socket = io.on('connection');
socket.emit('news', { hello: 'world' });
On the help page they say that Server#onconnection(socket:engine#Socket):Server expose a client, but i can't figure out how to use it. doc
this way i can use socket inside my other functions.
Right now on every function that i emiting stuff i do the io.on('connection', function (socket) all over again
Another question:
There is a way to different files emit event to each other
app.js
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
file1.html emit
<script src="socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost');
socket.emit('event15', function(x){
});
</script>
file2.html receive
<script src="socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost');
socket.on('event15', function(x){
});
</script>
On the server you have to deal with multiple sockets, one for each client. That's why on the server, you write an event handler that sets up the socket for each new client that connects. The basic idea is this:
Server:
io.on('connection', function(socket) {
socket.on('chat message', function(msg) {
io.emit('chat message', msg);
});
});
All of your message handling is supposed to be setup in the outer function. If you want to use the socket elsewhere however, you can pass it on like this:
io.on('connection', function(socket) {
socket.on('login', function(data) {
myServer.attemptLogin(socket, data.user, data.hash);
});
});
The attemptLogin(socket, user, hash) function can now process the parameters, then reply to the client by calling socket.emit()
If you want to store each user's socket in a variable, you need to create a data structure to do that. Here's an example:
var users = {}; // empty object
io.on('connection', function(socket) {
users[socket.id] = socket; // store socket for later use
// ...
});
This is only useful though if a connected user can somehow resume their previous session after a disconnect. Otherwise this is just a question of properly organizing your functions and variables.
As for the second question, you cannot send data from one HTML document to another. As soon as the user clicks a link leading to file2.html, a new socket will be opened and socket.io won't even realize that this is still the same user. As far as socket.io is concerned, some random dude just opened file2.html elsewhere in the world.
There are two ways around this: you can create a single-page app, where the user never actually navigates to another site. However they will still lose the connection if they accidentally close the tab, or navigate back, or refresh the page.
The other way is to use some kind of session information. If the user logs in, you can simply store their username and password hash in localStorage. On each page, check localStorage for the stored credentials, then send a "login" message to the server.

Forward all messages to socket.io room

If the client emits a message to a room, how can I get that message sent to all other clients in the room?
Currently on the server I have to do:
io.on('connection', function (socket) {
socket.on('join', function (room) {
socket.join(room);
socket.on('food.create', function (foods) {
socket.broadcast.to(room).emit('food.create', foods);
});
socket.on('food.update', function (foods) {
socket.broadcast.to(room).emit('food.update', foods);
});
socket.on('food.remove', function (foods) {
socket.broadcast.to(room).emit('food.remove', foods);
});
});
});
io.listen(3000);
Which is fine now there's only 3 messages, but when I add more it's going to get long. Does socket.io provide a way of automatically forward all messages form one client to all the other clients in that room?
This is the example off the socket.io docs page that shows how to use events and key:value sets for data:
var io = require('socket.io')();
io.on('connection', function(socket){
socket.emit('an event', { some: 'data' });
});
Why not use this construct to pass the messages around? Put your data in the data object along with what you want the respective client to do with that data as a key:value set. Something like this:
io.to('your_room').emit('food', { todo: 'delete', name: 'macaroni' });
});
The server can then simply broadcast that event to all other clients in the room. That way you don't have to filter messages on the server at all.
The other clients then receive the message and do whatever you want based on the data.todo and data.name values.

Socket.io event listening on a specific room

I'm not sure it is possible, but I really need that feature.
Can I do something like:
client side
sio = io.connect(url);
sio.to('room1').on('update', function(roomName) {
console.log(roomName);
});
Which will be triggered whenever an update event is emitted to 'room1'?
server side:
sockets.to('room1').emit('update', 'room1');
That, unfortunately, doesn't work just on the client side.
But you can do it on the server side.
Server Setup:
sockets.on('connection', function (socket) {
socket.on('join', function (room) {
socket.join(room);
});
});
//...
sockets.to('room1').emit('update', 'room1');
Client:
sio.emit('join', 'room1');
sio.on('update', function (room) {
console.log(room);
});
The "easy" way is to make sure that your emits from the server include the room in the payload.
sockets.to(room).emit('update', {room: room, message: 'hello world!'})
Most probably you want to do the same on the client-side of things.
So that the messages sent from the client to the server includes a room identifier so that the message can be routed correctly.
Or you implement name spaces.

Categories

Resources