let node.js/socket.io respond to two different events - node.js

I'm new to node.js and socket.io. I've got a test server with the sample code from the socket.io site (http://socket.io/#how-to-use) running.
I don't know where to begin to do the following
surf to "page1" and let node know that I'm user1
surf to "page2" and let node know that I'm user2
synchronise e.g. the words user1 and user2 type somewhere on "page1" and "page2" respectively and display all words from user1 as well as user2 on both pages.
I guess I need to create some sort of routing in the "handler" function, serverside of the node.js app, but I'm unsure if that's the correct way to go about it.
Can somebody help pointing me in the right direction please?

Socket.io makes this really easy. It has namespaces and rooms. For example to create two different namespaces you would do this:
var io = require('socket.io').listen(app, {origins: '*:*', log: false});
var page1 = io.of('/page1').on('connection', function (socket) {
//you can use socket in here
});
var page2 = io.of('/page2').on('connection', function (socket) {
//you can use socket in here
});
You now have two namespaces. Inside of each you could listen for events. For example if you sent a type event:
socket.on('type', function(text){
io.sockets.emit('type', {'whatWasTyped': text]);
}
This will send a type event to all connected clients.
If you wanted to separate the namespaces even more you could create rooms. Here is how to create a room in a namespace:
var page1 = io.of('/page1').on('connection', function (socket) {
socket.on('add', function(area){
socket.join(area);
};
});
You would have to send an add event everytime you made a connection with the room you would want to join. You can then send messages to only that room then.
io.of('/users').in(area).emit('event', {'event': yourInfoHere});
You can get info about the connection by running:
socket.get('user', function(err, info){ //in here });
This was some info off the top of my head and from the socket.io wiki: https://github.com/LearnBoost/socket.io/wiki/Rooms.
I haven't tested it, let me know if you have a few more questions.

Related

Adding a user to a room connected to a different server with Node, SocketIO and Redis

I am working on writing server-side code in node.js for a swift based iOS application. Currently, the code works when running it on one EC2 instance, but I am working on setting up a network load balancer so that it can more appropriately scale with incoming user traffic. I decided that the easiest way to achieve this is to use the redis adapter. So now, my server.js file includes:
const app = new Koa();
const server = http.createServer(app.callback));
const io = require('socket.io')(server);
const redisAdapter = require('socket.io-redis');
io.adapter({ host: 'my-elasticache-redis-endpoint', port: 6379 })
Based on the documentation, this seemed like the only step that was necessary from a code standpoint to get redis actually working in the project, but I may be missing something. From an architecture perspective, I enabled sticky sessions on the target group and set up two servers, both running this code. In addition, if I print out the socket io information, I can see that it has adequately connected to the redis endpoint.
The issue is as follows. Lets say I have two people, Person A and Person B, each connected to different servers. The application is supposed to function like so:
Person A adds person B to a socket room. Then the server emits an event to everyone in that room saying that person B has joined, so the front end can respond accordingly.
This is done through the following function:
protected async r_joinRoom(game: GameEntity, player: PlayerEntity): Promise<void> {
return new Promise((res, rej) => {
let socket: any;
socket = this._io.sockets.connected[player.socket_id];
if (!socket) {
socket = this._socket;
}
socket.join(`game/${game.id}`, (err: any) => {
if (err) {
return rej(new GameError(`Unable to join the room=${game.id}.\n${err}`));
}
res();
});
});
}
The premise here is that Person B is a player, and as a player, he has an associated socket id that the server is keeping track of. I believe the issue, however, is that socket = this._io.sockets.connected[player.socket_id]; Does not find the connected player, because he is technically connected to a different server. Printing out the socket shows it as null, and if I subsequently have that exact same function run on the server player B is connected to, he joins the room no problem. Therefore, when the emitted events takes place following 'adding' person B to the room, only person A's phone gets the event, and not B. So is this an issue with my Redis setup? Or is there a way to get all the connected clients to any of the servers running the node.js app?
I ended up answering my own question. When you add to the room, you have to do it directly from the adapter. From the documentation, that means I would switch socket.join... to
io.of('/').adapter.remoteJoin('<my-id>', 'room1', (err) => {
if (err) { /* unknown id */ }
// success
});
using that remoteJoin function worked off the bat

Websockets & NodeJS - Changing Browser Tabs & Sessions

I've started writing a node.js websocket solution using socket.io.
The browsers connects to the node server successfully and I get see the socket.id and all config associated with console.log(socket). I also pass a userid back with the initial connection and can see this on the server side to.
Question: I'm not sure the best way to associate a user with a connection. I can see the socket.id changes every page change and when a tab is opened up. How can I track a user and send 'a message' to all required sockets. (Could be one page or could be 3 tabs etc).
I tried to have a look at 'express-socket.io-session' but I'm unsure how to code for it and this situation.
Question: I have 'io' and 'app' variables below. Is it possible to use the 2 together? app.use(io);
Essentially I want to be able to track users (I guess by session - but unsure of how to handle different socket id's for tabs etc) and know how to reply to user or one or more sockets.
thankyou
The best way to handle the situation is rely on SocketIO's rooms. Name the room after the user's unique ID. This will support multiple connections out of the box. Then, whenever you need to communicate with a particular user, simply call the message function and pass in their id, the event, and any relevant data. You don't need to worry about explicitly leaving a room, SocketIO does that for you whenever their session times out or they close their browser tab. (We do explicitly leave a room whenever they log out though obviously)
On the server:
var express = require('express');
var socketio = require('socket.io');
var app = express();
var server = http.createServer(app);
var io = socketio(server);
io.on('connect', function (socket) {
socket.on('userConnected', socket.join); // Client sends userId
socket.on('userDisconnected', socket.leave); // Cliend sends userId
});
// Export this function to be used throughout the server
function message (userId, event, data) {
io.sockets.to(userId).emit(event, data);
}
On the client:
var socket = io('http://localhost:9000'); // Server endpoint
socket.on('connect', connectUser);
socket.on('message', function (data) {
console.log(data);
});
// Call whenever a user logs in or is already authenticated
function connectUser () {
var userId = ... // Retrieve userId somehow
if (!userId) return;
socket.emit('userConnected', userId);
}
// Call whenever a user disconnects
function disconnectUser () {
var userId = ... // Retrieve userId somehow
if (!userId) return;
socket.emit('userDisconnected', userId);
}

Socket.io rooms based on /link

I would like to implement rooms for chatting in my node.js project using socket.io. I would like to create a room based on any forward slash link visited that all people who access that link are joined in that rooms.
For example:
If my website is nodeapp.com, a room can be created by going to nodeapp.com/funroom. Anyone who goes to nodeapp.com/funroom (while there is at least 1 person in the room) can globally chat.
I do not need help implementing chat, just rooms.
Thanks a lot
1) You need send room name to server:
// Client
var socket = io('/');
socket.emit('joinroom', window.location.pathname);
socket.on("new user", function(data) {
console.log("New user. Total users: ", data);
});
2) You need get room name on server and join:
// Backend
var io = require('socket.io')();
var rooms = {};
io.on('connection', function(socket) {
socket.on('joinroom', function(room) {
this.join(room);
if (typeof rooms[room] ==== "undefined") rooms[room] = {};
rooms[room].count = rooms[room].total ? rooms[room].total+1 : 1;
io.to(room).emit("new user", rooms[room].count)
});
});
I have written a similar app for my university, using the following setup:
A user browses to nodeapp.com/desiredRoom. My express server has the following route:
app.get("/:roomName", function(res, req){
res.render("student", {room:req.params.roomName});
})
This renders a template (using Jade), with the input parameter room defined by the URL. I then pass this parameter to the client as a javascript variable. The Jade template looks something like this:
html
head
script.
room="#{room}";
script(src='/clientScript.js');
body
...
On the client side, clientScript.js now knows what room to join. You can use this system to perform more advanced room requests. For example, I split up the rooms based on multiple parameters in the URL.

Hosting multiple instances of a node.js server

I'm new to node.js and I'm working on learning how to use Socket.io to create multiple chat servers on my domain.
Here's the scenario:
you log onto the site
you pick a chat room or create a chat room
you join that individual chat room while other chat rooms are going on at the same time
Pretty standard operation on the web but I have yet to find a way to do it. Specifically, how to host it on your domain.
When creating and testing I always just use my localhost and tell the server to listen(8000) . However, how do write a script that:
A) creates a new listening port dynamically for each new chat sever?
B) how do I host it (I use Hostmonster)?
Instead of creating a separate server for each chat room, you could run all of them from the same server and just maintain a map of chat room name to the sockets involved in it.
For example,
//store a map of chat room name to sockets here
var chatRooms = {};
io.sockets.on('connection', function (socket) {
//when someone wants to join a chat room, check to see if the chat room name already exists, create it if it doesn't, and add the socket to the chat room
socket.on('joinChatRoom', function (data.message) {
var chatRoomName = data.message;
chatRooms[chatRoomName] = chatRooms[chatRoomName] || [];
chatRooms[chatRoomName].push(socket);
//set the chatRoomName into the socket so we can access it later
socket.set( "chatRoomName", chatRoomName, function() {
//when we receive a message
socket.on( "chatMessage", function(data) {
var chatMessage = data.message;
//figure out what chat room this socket belongs to
socket.get( "chatRoomName", function(err,chatRoomName) {
//iterate over the sockets in the chat room and send the message
chatRooms[chatRoomName].each(function( chatRoomSocket ) {
chatRoomSocket.emit("chatMessage", { message : chatMessage } );
});
});
});
});
});
});
Note, this code is untested and is just an idea (you should probably treat it more like pseudocode). There are a bunch of things it doesn't handle like cleanup upon disconnects, errors, etc. There are probably lots of other (and better) ways to accomplish this too but hopefully it'll give you some more ideas.

Destroying a handshake after logout. socket.io

Hello I am trying to build chat into an application. What I am wondering is when the user logs out of the website how do I also destroy the socket.io handshake associated with that session so the user cannot send messages from say another tab when he is logged out.
I am using expressjs if that is any help.
Well in case anyone ever find this and wants to know I did figure it out.
You can access the sockets disconnect function. I had object of users ids and their socket id so when someone logged out I called
app.get("/logout", function(req,res){
//do other logging out stuff
sockets.disconnectUser(req.session.user_id);
}
// Disconnect User function
sockets.disconnectUser = function(user_id){
sockets.socket(users[user_id]).disconnect();
}
The socket.io object contains information about all connected sockets and the sessionID of each socket. Thus, it is possible to iterate through the connected sockets and disconnect those which are associated with the sessionID that is logging out. There is no need to manually track user and socket ids in this approach.
Example code tested with socket.io#2.2.0, express#4.17.1 and express-session#1.16.2.
const SocketIO = require('socket.io');
let sio = new SocketIO;
app.get('/logout', function(req, res) {
//do other logging out stuff
logoutSocketsIO(req.sessionID);
});
// Iterate through all connected sockets and close those which are associated
// with the given sessionID
// Note: One sessionID can have multiple sockets (e.g. many browser tabs)
function logoutSocketsIO(sessionID) {
let connections = sio.sockets.connected;
for(let c in connections) {
let socketSessionID = connections[c].conn.request.sessionID;
if(sessionID === socketSessionID) {
connections[c].disconnect();
}
}
}

Resources