Advice on implementing "presence" for a web site? - node.js

Ideally, I'd like to find simple, lightweight code that allows all the web clients connected to my site to maintain real-time status of who else is currently online.
I know ejabberd does this, but it also does a lot of other things, and I'd prefer a small code footprint so I can customize and understand its performance characteristics.
I like the non-blocking aspect of node.js, and was wondering if there's an open source project that does all this logic.
I'd also like to see a JavaScript implementation of maintaining this model on the client side.

For real time status, use socket.io. Every time someone connects add them to the connected users list. For accurate stats you will need to keep track of user sessions. See http://www.danielbaulig.de/socket-ioexpress/
var onlineUsers = {};
var online = 0;
io.sockets.on('connection', function (socket) {
onlineUsers[socket.handshake.sessionID] = socket.handshake.session;
online = Object.keys(onlineUsers).length;
socket.broadcast.emit('online', online);
socket.on('disconnect', function () {
delete onlineUsers[socket.handshake.sessionID];
online--;
socket.broadcast.emit('online', online);
});
});

For anyone reading this in the future. I started with the answer written by fent, but I needed some modifications since things have changed in the intervening 6 years since his answer was published.
I used a Map rather than an Object for storing the session information. this means that the sessionID is no longer required.
const onlineUsers = new Map();
io.on('connection', function (socket) {
console.log('connection initiated');
socket.on('userInfo', userInfo => {
console.log('userInfo', userInfo);
onlineUsers.set(socket, userInfo);
io.emit('onlineUsers', Array.from(onlineUsers.values()));
});
socket.on('disconnect', function () {
console.log(onlineUsers.get(socket));
onlineUsers.delete(socket);
io.emit('onlineUsers', Array.from(onlineUsers.values()));
});
});

Related

socket.io room size 4.x

So i've been making a realtime E2EE chatting application in node.js and socket.io, and I've ran into an issue
I'm using a recent socket.io version, specifically 4.2.0 and I've been attempting to find out the number of clients within a room, in order to create a check to either allow, or block access to a chat area, since it doesn't support more than 2 clients in 1 room.
My variables/includes for socket.io
var socket = require("socket.io")
var io = socket(server)
var chat = io.of('/socket').on('connection', function (socket) {
socket.on('loginchat', async function(data){
io.in(data.id).allSockets().then(result=>{
console.log(result.size) })
});
});
data.id is a randomly generated string and used to create a room server side, so my idea was to use allSockets() to get a count of sockets connected to that room, but it returns zero even with 2 clients connected.
allSockets() idea from https://stackoverflow.com/a/66065685/16140221
I can't find a working solution on stack overflow, or anything inside the docs that is an obvious method to do so. I'm not the best at socket.io since I simply picked it up to use for this project, so any help is appreciated :)
Given the example you provided, you don't seem to ever make a socket to join a room. So your room is forever empty.
According the socket.io documentation you must first listen for the connection event, then you must use join to add users to the room.
const socket = require("socket.io")
const io = socket(server)
io.on("connection", socket => {
socket.on('loginchat', async data => {
//Assuming data.id is the room identifier
const roomUsers = await io.in(data.id).allSockets();
//There, you make your socket join the room if room users are not exceeding
//your maximum
if(roomUsers.size < 2) socket.join(data.id);
});
});
var socket = require("socket.io")
var io = socket(server)
var chat = io.of('/socket').on('connection', function (socket) {
socket.on('loginchat', async function(data){
const sockets = await io.of("/socket").in(data.id).fetchSockets();
console.log(sockets.length)
if(sockets.length <= 1){
socket.room = data.id;
socket.join(data.id);
}else{
//attempting to join a room with greater than 2 users
console.log("too many")
}
});
});
https://socket.io/docs/v3/migrating-from-3-x-to-4-0/
Using the fetchSockets() and checking the length of the object, you are able to check the length or numbers of users in a room.
Working in Socket.io v4.3.1 but most likely functioning in any v4.x

Socket.IO socket protection without authentication

I've created a simple Node.js app using Express.js and socket.io (available here), where the user clicks a button, and it increments a number on the page. This number is also incremented live among all clients connected to the page. I am using web sockets and socket.io to get the client-server communication and live number updating system.
I am using the flood-protection module to limit socket emits to 5 per second, but this really doesn't make the game very fun because of the low amount of clicks per second you can have, and hackers could just use a setInterval and still make considerable progress automatically, even at such a low rate.
My issue:
I don't want the user to have to authenticate themselves - anybody should be able to play!
I want to keep the click rate around 15 clicks per second, if possible.
I don't want people to be able to send socket messages and automatically click the button from the browser console.
Here's the program:
index.js
var express = require("express");
var http = require("http");
var socketIO = require("socket.io");
var path = require("path");
var fs = require("fs");
var FloodProtection = require("flood-protection").default;
__dirname = path.resolve();
function btoa(str) {
return new Buffer(str, 'latin1').toString('base64');
};
function atob(b64Encoded) {
return new Buffer(b64Encoded, 'base64').toString('latin1');
};
var app = express();
app.get("/", function(req, res){
res.sendFile(__dirname + "/index.html");
});
var temp;
num = temp | parseInt(atob(fs.readFileSync("num.txt"))) | 0
var server = http.createServer(app);
var io = socketIO.listen(server, {log: true});
io.sockets.on("connection", (socket) => {
protector = new FloodProtection({rate: 5, per: 1})
io.sockets.emit("update", num);
socket.on("push", (value) => {
if (protector.check()) {
num++;
temp = num
io.sockets.emit("update", temp);
} else {
io.sockets.emit("update", "You can only click the button five times per second.")
socket.disconnect(2)
setTimeout(()=>{}, 3000)
}
});
socket.on("disconnect", () => {
fs.writeFile("num.txt", btoa(String(temp)), (err) => {
if (err) throw err;
console.log("saved | new num: " + temp);
})
})
});
server.listen(5000);
index.html
<html>
<head>
<title>A Button</title>
</head>
<body>
<button onclick='push();'>Click me!</button>
<p id="out"></p>
</body>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var variableFromFrontEnd = 2;
var socket = io.connect("/");
socket.on("connect", function() {
socket.on("update", function(val) {
document.getElementById("out").innerHTML = val
});
});
socket.on("disconnect", function() {
setTimeout(()=>{socket.connect();}, 1000);
});
function push() {
if (socket.connected) {
socket.emit("push");
}
}
</script>
</html>
num.txt is a base-64 encoded number.
So, is there a way to be able to do this without significant rate limiting or authentication? Or am I just going to have to use rate limiting?
There's a lot of different ways for users to cheat, and just as many ways to prevent them. The general rule is that you can only "make things harder" (if you're lucky, hard enough that the potential cheater loses interest before succeeding).
For a browser-based game, I would make sure that you are at least ensuring your game gets totally minified/tersed (so your javascript code is as unreadable as possible, and it's more difficult to just call a "click" function directly), and build in checksums in your messages (so the user can't just make socket calls directly to the server).
Once you've done that, you still have to deal with users who generate click events on the element directly with code or a plugin, or users who use a program outside the browser to generate click events above the button repeatedly. Your best defense against this is not to prevent it, but instead to detect it -- probably on the server side, by watching for users that have a sustained click rate that is not humanly possible, and then blowing up their game / temporarily banning their IP / etc.
See a related question Ways to prevent or reduce cheating for more related ideas (this question is about general client-server games, not browser games, but some of the discussion is still useful).

Session Management Socket.io

I'm trying to implement sessions in my node.js app which makes heavy use of socket.io. I'm struggling with this currently, and found a rather simple solution which recommends using the socket itself to store the session information. For example:
socket.on('login', function(username) {
if (loginSuccessful() {
socket.sessionID = generateSessionId();
}
});
socket.on('logout', function(username) {
socket.sessionID = null;
});
I am worried that such a simple solution wouldn't be secure, however, I'm not sure if there is any clear vulnerability this solution would have.

broadcast client disconnect to a particular room

I am following this excellent Tutorial to make my first test game in node.js
Everything is working perfectly. Even when I changed it from two player to three player, still worked fine. But unfortunately the tutorial did not cover the disconnect event. Every event on server is happening against some client action. e.g join game, answer click etc.
But i am stuck at broadcasting to all clients in a room when one of the user looses his internet connection because he performs no action to do that. By some search i found something like following
// Listen for Socket.IO Connections. Once connected, start the game logic.
io.sockets.on('connection', function (socket)
{
//console.log('client connected');
initGame(io, socket);
io.sockets.on('disconnect', function ()
{
socket.broadcast.emit('playerDisconnected', 'x has disconnected');
});
});
This is not working. And I cant understand how to know and how to tell users in a room that who has disccented. Please guide me what i can do more to make question clear.
Edit
With the help of given answer I am able to broadcast a disconnect event to all clients connected to the server. But what can i do to emit only to the particular game clients mean from where i could get gameid/roomid in this event. I see something like game socket in my working code/ (following is it). but I tried simple socket.emit('playerDisconnected', 'x has disconnected'); it does not work and emit.broadcast works but not as i need
function initGame(sio, socket)
{
io = sio;
gameSocket = socket;
gameSocket.emit('app_ev_connected', { message: "You are connected!" });
gameSocket.on('serv_ev_playerJoinGame', playerJoinGame);
gameSocket.on('serv_ev_getRooms',getRooms);
}
Don't you need to add the disconnect event on the socket itself?
io.sockets.on('connection', function (socket) {
initGame(io, socket);
socket.on('disconnect', function () {
//socket.broadcast.emit('playerDisconnected', 'x has disconnected');
socket.broadcast.emit('playerDisconnected', this.id + ' has disconnected');
//if you want to emit in particular room
io.sockets.in('roomName').emit('playerDisconnected', this.id +' has disconnected');
});
});
I had a suggestion which makes more sense in a comment but since I don't have enough reps I couldn't add it.
So I use the userData from my JWT authentication middleware where I put the room number and regenerate the token where I push the room details too!
More on SocketIO Middleware
More on JWT Auth

Node Express design for avoiding multiple clients in Socket.io

Building a simple app with Posts and Comments.
User hits a Post and adds a comment, everyone who's viewing that same Post will see the new comments added to the post. All done via Socket.io.
This is how I instantiate it:
// app.js
var io = require('socket.io').listen(app.listen(app.get('port')));
var chat = io.of('/p/' + id).on('connection', function(socket) {
socket.on('addComment', function(data) {
var content = data.comment;
var comment = new commentModel({
user: req.user._id,
post_id: id,
content: content
});
comment.save(function(err, doc) {
if (err) {
return next(err);
}
commentModel.findById(doc._id).populate('user').exec(function(err, comment) {
chat.emit('newComment', {
comment: comment
});
});
});
});
});
// client-side JS
self.socket_io = io.connect(window.location.href);
self.socket_io.on('newComment', function(data) {
// do stuff with the new comment
};
The first time you hit the page, the comment is submitted once (one socket connection), however, if I refresh the page and submit a new comment, two are created (one for each connected client).
Any ideas where I'm going wrong?
does
commentModel.findById
nede to be
comment.findByID
?
You could try a few of these:
Manage the socket session on the server: socketio_session[user_id].push(session).
Will give you access to all the sessions connected for a particular user. You probably don't need to store a list of sessions per user, just the latest session, and force disconnect on the existing session, before storing a new session.
--
This list a few things, including changing a config for forcing new connection
https://github.com/LearnBoost/socket.io/issues/474
--
This might also be of use, explains how to extract session id in sockets using express:
http://www.danielbaulig.de/socket-ioexpress/
I think this one might help you out creating new socket connection each time user refresh
https://github.com/wcamarao/session.socket.io/

Resources