Node.js with Socket.io - Pairing all users connected - javascript

I'm new to the MEAN stack and trying to build a web app with it. With socket.io, I would like to randomly pair up all the users that are currently connected to the page.
My approach would be to use random integers in the range of the length of a list of all users connected, as indices of the randomly selected users in the array.
I know this is something that I can do to keep track of all clients/sockets connected:
io.sockets.on('connect', function(client) {
clients.push(client);
client.on('disconnect', function() {
clients.splice(clients.indexOf(client), 1);
});
});
But would it have issues with scaling? If so, what would be a better approach? Please advise.

Related

Socket.io join another client to a room

My goal is when A user click on B user, they'll join in a room. I'm holding socket.id in array for all clients.
//CLIENT SIDE FOR A//
$('.chat-start').on('click', function(event) {
event.preventDefault();
var room_id = '72xaz132s'
var target_id = $('.target').data('id');
socket.emit('force join room', room_id, target_id);
});
//SERVER SIDE FOR A AS AN EXAMPLE//
var clients {'customIDA' : socket.id for a, 'customIDB' : socket.id for b}
socket.on('force join room', function (roomid, customIDB) {
socket.join(chat_id); //CLIENT A JOINS ROOM//
})
Client A joins room without problem, but how can i add also client B on same room ?
In io.on("connection") you can store socket object with user id as a object
{"socket": socket, "id": user_id}
to array. Then find index from array:
let user_index = users.findIndex(user => user.id == user_id);
And finally join to room.
let socketB = users[user_index].socket;
socketB.join('room');
You can call join to subscribe the socket to a given channel:
// On connection join room
io.on('connection', function(socket){
socket.join('ROOM ID');
});
And then simply use to or in (they are the same) when broadcasting or emitting:
io.to('some room').emit('some event');
To leave a channel you call leave in the same fashion as join.
Documentation: https://socket.io/docs/rooms-and-namespaces/
function join() {
// Run's once clicked
console.log('Join function here');
}
Join
Maybe handle it server side? Have a dictionary that stores User Ids and the Socket Id, you can store this information io.on("connection"). Then when User A does the process of creating the room, send to the server side the room id and something that identifies User B. Server side figures out the socket id of User B and joins them to the room.
This method is a bit of a hack, but it does allow you to force any specified socket into a room entirely server-side, once you have their socket ID, without maintaining another object array. SocketIO (v2.x) stores room allocations in two places to make things fun.
// $targetSocket is the socket ID of the connection you want to force into a room
$roomArray = $io->sockets->connected[$targetSocket]->rooms; //socket's rooms stored here
$roomArray[$newRoom] = $newRoom; // add the new room to this array
$io->sockets->connected[$targetSocket]->rooms = $roomArray; // save the modified array
$io->sockets->adapter->add($targetSocket, $newRoom); // update the adapter too
This is obviously PHP but it will translate easily into js.
You can do the reverse to kick users out of rooms too:
unset($roomArray[$oldRoom]); //delete room key and value from this socket's array
// save the array here, see above
$io->sockets->adapter->del($targetSocket, $oldRoom); // delete from adapter
Use both at once for a leave and join system.
This may seem pointless when a socket can add or remove itself so easily with socket->join('ROOM'); but there are circumstances in which you may need to do this from outside the connection's socket, such a game function.
For anyone facing a similar issue.
#Kouvolan Arnold's solution will work but having to store the socket object for each user is too much of a resource, especially when socket.io already stores them.
A simple solution here is for you to get the socket object already stored by locating it. The good news is that socket.io already provides that, see below.
//v2.x or less
io.sockets.sockets[USER_SOCKET_ID_HERE]
//v3.x and 4.x
io.sockets.sockets.get(USER_SOCKET_ID_HERE)
The code below would have worked well in your case.
//v2.x or less
io.sockets.sockets[clientSocketId].join(roomid);
//v3.x and 4.x
io.sockets.sockets.get(clientSocketId).join(roomid);
Thanks

Socket.IO: Remove specific socket_id from room?

I have a specific socket_id that I want to remove from a room in socket.io. Is there an easy way to do this? The only way I can find is to disconnect by the socket itself.
From the client, you send your own message to the server and ask the server to remove it from the room. The server can then use socket.leave("someRoom") to remove that socket from a given room. Clients cannot join or leave rooms themselves. Joining and leaving to/from rooms has to be done by the server (because the whole notion of chat rooms only really exists on the server).
Documentation here.
For socket.io version 4.0.0 upwards (Mine is version 4.4.0):
Step #1) You have to get an array of the sockets connected to that socket.id as shown in the code below.
//You can replace the userStatus.socketId to your own socket id, that was what I
//used in my own case
const userSockets = await io.of("/chat").in(userStatus.socketId).fetchSockets();
Step #2) After getting all the socket IDs, you will be given an array containing the individual socket (I got to know this by logging the results from the async and await fetchSockets() method that the io library on the server offered). Then use the .find() method to return the single socket object matching that same socket id again as shown below.
const userSocket = userSockets.find(socket => socket.id.toString() === userStatus.socketId);
// Replace userStatus.socketId with your own socket ID
Step #3) Remove the socket by leaving the group using the .leave() method of the socket.io library as shown below.
userSocket.leave(groupId);
// groupId can be replaced with the name of your room
Final Notes: I hope this helps someone in the future because this was what I used and it worked for me.

Node JS live result from MySQL

I'm trying to show the results from my DB in table. I want the users to see every change made in the table(insert, update or delete) live without refreshing the page.
My first question is how to fetch my DB data in table like this. I also would like actions taken by the users to be lively visible from other users(like edit, delete, create).
This is my server.js
var io=require('socket.io'), mysql=require('mysql');
var express = require('express');
var connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '',
database : 'lolast'
});
connection.connect(function(err) {
if(err) {
console.log('error when connecting to db:', err);
setTimeout(handleDisconnect, 2000);
}
});
var ws=io.listen(3000);
var socketCount = 0
ws.on('connection', function(socket) {
socketCount++
// Let all sockets know how many are connected
ws.emit('users connected', socketCount);
socket.on('disconnect', function() {
// Decrease the socket count on a disconnect, emit
socketCount--
ws.emit('users connected', socketCount)
});
});
And this is my client side:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://127.0.0.1:3000/socket.io/socket.io.js"></script>
<script>
$(document).ready(function(){
// Connect to our node/websockets server
var socket = io.connect('http://localhost:3000');
// New socket connected, display new count on page
socket.on('users connected', function(data){
$('#usersConnected').html('Users connected: ' + data)
})
})
</script>
<div id="usersConnected"></div>
My user counter is working fine, show the result in live but I having trouble with my sql data.
I will be very greatfull if someone helps me.
The answer is replication logs or bin-logs on mysql. Have a look at this repo: https://github.com/numtel/mysql-live-select
This is an incomplete answer but can at least get you started on the problem -
MySQL doesn't have any sort of out-of-the-box support for this. Whatever you do will need to be based on polling, which is not impossible to scale, but requires very smart use of MySQL to scale well.
There are better storage products for this. I think Firebase specializes in this (bought by Google?) and I'm positive there is an AWS-product that does this.
Even NoSQLs like Redis have push support. I think MongoDB does.
There are probably other middleware solutions. There might be frameworks that will create and maintain the tables themselves. Try searching for language-agnostic frameworks or frameworks in other languages and see if anything points you in the right direction.
The roll-your-own solution will look something like this: all of your queries that should be visible to other users will need to go into tables, or be kept track of in tables, with timestamps, that your clients can query for anything since they last received data. This is hard to scale without serious MySQL chops (but reasonable to scale if you know MySQL well). (Just making these claims from my experience at a MySQL startup).
I am using firebase for getting live events for changing value in database.
For view perspective I have hooked firebase with Bootstrap's DataTable.
You can check small use of it over here. Firebase is one of the best solution for this.
https://www.firebase.com/docs/web/libraries/angular/guide/synchronized-arrays.html

Streaming data from the Server to Client with Meteor:

I'm developing a text based adventure game with Meteor and I'm running into an issue with how to handle certain elements. Namely, how to emit data from the Server to the Client without any input from the Client.
The idea is that when a player is engaged in combat with a monster, the combat damage and updating the Player and Monster objects will be occurring in a loop on the server. When the player takes damage it should accordingly update the client UI. Is something like this possible with Publish / Subscribe?
I basically want something that sits and listens for events from the server to update the combat log accordingly.
In pseudo-code, this is something along the lines of what I'm looking for:
// Client Side:
Meteor.call("runCommand", "attack monster");
// Server Side
Meteor.methods({
runCommand: function(input) {
// Take input, run the loop to begin combat,
// whenever the user takes damage update the
// client UI and output a line saying how much
// damage the player just received and by who
}
});
I understand that you can publish a collection to the client, but that's not really as specific of a function I'm looking for, I don't want to publish the entire Player object to the client, I just want to tell the client to write a line to a textbox saying something like "You were hit for 12 damage by a monster!".
I was hoping there was a function similar to SocketIO where I could, if I wanted to, just emit an event to the client telling it to update the UI. I think I can use SocketIO for this if I need to, but people seemed to be adamant that something like this was doable with Meteor entirely without SocketIO, I just don't really understand how.
The only outs I see to this scenario are: writing all of the game logic client-side which feels like a bad idea, writing all of the combat logs to a collection which seems extremely excessive (but maybe it's not?), or using some sort of SocketIO type-tool to just emit messages to the client to tell it to write a new line to the text box.
Using Meteor, create a combat log collection seem to be the simplest option you have.
You can only listen on added event and then clear the collection when the combat is over.
It should be something like this :
var cursor = Combat_Log.find();
var handleCombatLog = cursor.observe({
added: function (tmp)
{
// do your stuff
}
});
I ask a similar question here, hope this will help ^^
Here's how I did it without a collection. I think you are right to be concerned about creating one. That would not be a good idea. First install Streamy.
https://atmospherejs.com/yuukan/streamy
Then on the server
//find active sockets for the user by id
var sockets = Streamy.socketsForUsers(USER_ID_HERE)._sockets
if (!Array.isArray(sockets) || !sockets.length) {
//user is not logged in
} else {
//send event to all open browser windows for the user
sockets.forEach((socket) => {
Streamy.emit('ddpEvent', { yourKey:yourValue }, socket);
})
}
Then in the client, respond to it like this:
Streamy.on('ddpEvent', function(data) {
console.log("data is ", data); //prints out {yourKey:yourValue}
});

Publishing an array from the server to the client

I have an array in if Meteor.isServer that pushes userIds to the array when they login and removes said userid on logout. In order to do this I am using the Profile-Online package (https://github.com/erundook/meteor-profile-online). I want to make unique templates depending on the amount of users being 0, 1 or 2 users connected I need to make the array in the server side available to the client without it I can't make templates based on the amount of users. I tried to publish the array in the server and subscribe in client. I have an idea I'm doing something totally and completely wrong. My main objective is to make the array(playerArray) accessible from the client.
JS file:
Games = new Meteor.Collection("games")
if (Meteor.isClient) {
//testing if subscribe returns IDs from the array which it doesn't
Meteor.subscribe("ingame",function(test){console.log(test)})
}
if (Meteor.isServer) {
playerArray = []
Meteor._onLogin = function (userId){
if(!_.contains(playerArray, userId)){
playerArray.push(userId)
}
console.log(playerArray)
}
Meteor._onLogout = function (userId){
playerArray = _.without(playerArray, userId);
console.log(playerArray);
}
Meteor.publish("ingame",function(){
return playerArray
})
}
I'm quite the newbie at meteor, but this should work:
create a collection CurrentUsers into which you insert on _onLogin and from which you remove on _onLogout,
then on the client's side ask ClientUsers.find({}).count() and it return the number of logged in users
I suspect that the array approach isn't scalable (won't work for multiple node processes), but this should work OK

Categories

Resources