Broadcasting with shoe sockjs - node.js

I am trying to setup a simple scenerio using shoe + dnode +sockjs and I do not know how to broadcast a message to all users connected to the web application.
Do you know if there is a function or method which manage this? or should it be make by "hand"?

AFAIK, you have to roll it by "hand" as you say. Here is what I do:
server.js:
var shoe = require('shoe')
var connectedClients = {}
var conCount = 0
var sock = shoe(function(clientStream) {
clienStream.id = conCount
connectedClients[clientStream.id] = clientStream
conCount += 1
})
somewhere else in your server-side program:
//write to all connected clients
Object.keys(connectedClients).forEach(function(cid) {
var clientStream = connectedClients[cid]
clientStream.write(yourData)
})
Note, you'll want to introduce additional logic to only write to connected clients, so you'll want to remove disconnected clients from connectedClients, something like delete connectedClients[id].
Hopefully that helps.

Related

Parsing TCP packet buffers in Node.js

I am trying to create a TCP game server (for an old version of Minecraft), and I want to use Node.js as the server software. I have created a basic socket server, and I am wondering what the best way to go about parsing the data buffers would be?
Here's my code right now:
const net = require('net')
const serialize = require('node-serialize');
const server = net.createServer();
server.on('connection', (socket) => {
socket.on('data', (buffer) => {
let packetID = buffer.readUInt8(0);
console.log(`Packet ID: ${packetID}`)
switch (packetID) {
case 1:
// Login Request
// Log some info about the request
let protocol = buffer.readUIntBE(2, 3)
let playername = buffer.slice(7, buffer.length).toString()
console.log(`Player name: ${playername}`)
console.log(`Client protocol version: ${protocol}`)
console.log(buffer)
// Send login confirmation packet
var packetInfo = [0x01, 0]
var entityID = intToByteArray(1298)
var seed = intToByteArray(971768181197178410)
var mode = [0]
var dimension = [0]
var difficulty = [1]
var height = [128]
var maxPlayers = [8]
var buff = Buffer.from([].concat.apply([], [packetInfo, entityID, seed, mode, dimension, difficulty, height, maxPlayers]))
console.log(`Bytes: ${buff.byteLength}`)
//socket.write(buff)
// Disconnect/Kick Packet
var buffr = stringToByteArray("§eKicked from the server (cause world doesn't load yet)")
var packetbugger = [0xFF, 0, buffr.length / 2, 0]
var finalbuffr = Buffer.from([].concat.apply([], [packetbugger, buffr]))
socket.write(finalbuffr)
case 2:
// Handshake
// Sends a "-"
console.log(serialize.unserialize(buffer).Username);
socket.write(Buffer.from([0x02, 0, 1, 0, 0x2d]))
}
})
socket.on('error', (error) => {
console.error(error)
})
})
server.listen("25565", "localhost")
I think that there's probably a better way to parse packets than what I am doing. Every packet that Minecraft sends to the server has it's own id, as detailed on wiki.vg. Here's a sample packet structure for a server to client packet:
How would I be able to retrieve the readable data from a packet buffer in Node.js? For example, I'd like to be able to extract the player name string from the packet. However, the most important thing for me would be extracting the packet ID in hex form. I have tried, but I am unable to do so. Any help or suggestions as to how to go about this would be massively appreciated.
Also, how would I be able to craft my own packet in the same format as the picture above? All of these are questions I am struggling to find good answers to.

NodeJs Socket programming how to handle and manage Sockets? (Without Using socket.io) What is the efficient way?

After connection to the Server, every time Data coming from this connection (Socket.on('data',...)), Server fetches UserID from Data and check the ClientList (array of Socket objects), to see if Socket with this UserID exists in ClientList, if not : adds UserID as a property of Socket and then adds Socket object to Client list.
So when user with ID=1 want to send a message to user with ID = 2,
Server search for Socket with UserID = 2 in ClientList to find the right Socket and send user 1's message to the found Socket (user 2's socket).
I'm trying to accomplish this without using socket.io! That's what my employer made me to do! :))
Now my question is: am I doing this right? Is this efficient to check ClientList array (every time a connection send Data) to see if this UserID exists in ClientList? if not, then what is the right and efficient way? there is no problem with my code and it works. but what if there are thousands of connections?
Any Sample code , example or link would be appreciated. Thank you.
here is a pseudo code :
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
Socket.UserData = {}; // I want to add user data as a property to Socket
Socket.on('data', function (Data) {
var userID = myAuth.Authenticate_and_getUserID(Data);
if(userID != undefined){
var found = false;
ClientList.filter(function(item){
// check if Socket is in ClientList
if(item.UserData.ID == userID){
// client was connected before
found = true;
}
});
if(!found){
// this is a new connection, Add it to ClientList
Socket.UserData.ID = userID;
ClientList.push(Socket);
}
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
UPDATE for clarification:
is this efficient to look into ClientList every time Data is coming to Server, to check for receiverID (presence of receiver) and to Update ClientList with current connection UserID if not exists?
how should I manage new connections(users) and store them in server for later use when number of users are thousands or millions! NOT 10 or 100. How socket.io is doing this?
later usages could be:
check to see if one specific user is online (have an object in ClientList)
send realtime message to a user if he/she is online
etc . . .
Actually I am doing this wrong!
Arrays in JavaScript are passed by reference! So there is no need to update ClientList every time a Socket send data.
Therefor the Code changes like Following:
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
ClientList.push(Socket);
Socket._isAuthorized = false;
// when socket send data for the first time
// it gets authenticated and next time it send data
// server does not authenticate it
Socket.on('data', function (Data) {
var userID = getUserID(Data);
if(Socket._isAuthorized != true){
if(authenticate(Socket)){
Socket._isAuthorized = true;
Socket._userID = userID;
return;
}
}
// do something with data...
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
And its efficient!

Best Approach to send real time analytics using socket.io

I am using socket.io and node.js/mongoDB for an app which will send real time analytics between Parents and Drivers
Let's say Driver is moving along a path and for every location change he will send his location to a list of specific parents.
I can think of one approach to achieve such functionality
1- I create two arrays
var userSockets = {};
var driverSockets = {};
Whenever a user/driver is connected i do
For Driver - driverSockets[accId] = socket
For User - userSockets[accId] = socket
Now if a driver has to emit a location change, he will do something like
userSockets[userId].emit(abc)
I would like to know if this approach is better? Would it be better to save users as onlineUsers in MongoDB but even then how to access their sockets to emit data.
What would be the best approach.
You should use "room" to store online user and emit to this room(channel) when location change.
//join a room(channel)
socket.join('online');
//sending to sender client, only if they are in 'online' room(channel)
socket.to('online').emit('location', {user_type:'driver'});
Here is the example code to seperate driver and parents in different array with user_type key-word and send driver location to all the parents in real time. Furthermore, We can add users mobile number to send specific driver location to a specific parent etc.
var parents = [];
var drivers = [];
var users = [];
io.on('connection', function(socket){
users.push(socket);
socket.on('disconnect', function(msg){
var i = users.indexOf(socket);
users.splice(i, 1);
var j = parents.indexOf(socket);
if(j !== -1){
parents.splice(j, 1);
}
var k = drivers.indexOf(socket);
if(k !== -1){
drivers.splice(k, 1);
}
});
socket.on('register', function(msg){
console.log(msg);//send json- {"user_type":"driver"}
var data = JSON.parse(msg);
var i = users.indexOf(socket);
if(data.user_type === 'driver'){
drivers.push(users[i]);
}else{
parents.push(users[i]);
}
users[i].emit('register', '{"status":"registered"}');
});
socket.on('location', function(msg){
console.log(msg);//send json- {"user_type":"driver","location":"lat,long"}
var data = JSON.parse(msg);
if(data.user_type === 'driver'){
for(var x=0;x<parents.length;x++){
parents[x].emit('location', '{"user_type":"driver", "location":"lat,long"}');
}
}
});
});

How to create a MySQL adapter for session.io in NodeJS?

I have a NodeJS project on 2 Google Cloud instances behind a load-balancer. I'm using socket.io. I want to share the sessions between the instances.
Usually developers doing it using socket.io-redies, but I don't want redis just for that. I have Cloud SQL (Aka: MySQL), and I want to use MySQL for sharing the sessions.
I have understand the whole index.js of the redis adapter file, except this function:
https://github.com/socketio/socket.io-redis/blob/master/index.js#L93
Redis.prototype.onmessage = function(channel, msg){
var args = msgpack.decode(msg);
var packet;
if (uid == args.shift()) return debug('ignore same uid');
packet = args[0];
if (packet && packet.nsp === undefined) {
packet.nsp = '/';
}
if (!packet || packet.nsp != this.nsp.name) {
return debug('ignore different namespace');
}
args.push(true);
this.broadcast.apply(this, args);
};
If I need to get events from the MySQL (subscribe) I think it is not possible. Am I right?
Do you know another solution of sharing socket.io between two machines, without using Redis?

Determine whether the domino server is running or not ( session.getDatabase(..) takes too long when the corresponding server is down )

I have approximately 4-5 users on different servers and need to find free time based on the calendar entries. Till last night the functionality was working great, however, today we just figured out a small issue/bug. One of the server machine was offline today and SSJS took around a minute (60 seconds) to figure that out.
Is there any such option to verify whether the server is running? I am asking this since session.getDatabase takes a long time to provide any results in such case.
Just for reference, below is my code to verify users mail database accessibility. It works perfectly fine when all servers are up, however, the waiting time is too long when any server is down.
// Set up the names
var names = new java.util.Vector();
var inaccessible_calendars = new java.util.Vector();
var infoDoc:NotesDocument= database.getDocumentByUNID(context.getUrlParameter('refId'));
var members:java.util.Vector = infoDoc.getItemValue("members");
names = infoDoc.getItemValue("members");
//var members:java.util.Vector = infoDoc.getItemValue("members");
var membersIterator = members.iterator();
var maildb ="";
while(membersIterator.hasNext()){
var val = membersIterator.next();
var nab:NotesDatabase=session.getDatabase(database.getServer(),"names.nsf")
var nview:NotesView=nab.getView("($NamesFieldLookup)")
var doc:NotesDocument=nview.getDocumentByKey(val,true)
if(doc){
var email = doc.getItemValueString("MailFile")
var emailServer = doc.getItemValueString("MailServer")
doc.recycle()
nview.recycle()
nab.recycle()
maildb=#LowerCase(emailServer)+'!!'+#LowerCase(email)+ '.nsf'
var emailServerCN = #Name("[CN]",#LowerCase(emailServer));
var emailDBName = #LowerCase(email)+ '.nsf';
emailDBName = emailDBName.replace("\\","\/");
try{
if(session.getDatabase(emailServerCN.toString(),emailDBName.toString())==null){
names.remove(val);
inaccessible_calendars.add(val);
}else{
var emailDB:NotesDatabase = session.getDatabase(emailServerCN.toString(),emailDBName.toString());
if(!(emailDB.getCurrentAccessLevel()>0)){
names.remove(val);
inaccessible_calendars.add(val);
}
}
}catch(err){
names.remove(val);
inaccessible_calendars.add(val);
}
} else {
nview.recycle()
nab.recycle()
maildb= ""
}
}
if(!inaccessible_calendars.isEmpty())
return "Following calendars are not accessible"+inaccessible_calendars.toString()+"*";
Any help would be really appreciated.
Are these database in a cluster? Can they be? If so then when you open it there an "OpenWithFailOver" method I forget the exact syntax. But if the database in unavailable it should open another in the cluster.

Resources