Storing socket.io emits in global array + Node.js - javascript

Is there a way to store all emits of socket.io in a global array so that I can loop through the emits when a new user joins the website and can pickup where the 'canvas drawing' currently is. I want the new user to see what work has already been done and then collaborate on it.
Any other ways I could approach towards this?

If you just want the emits stored for the duration of the server running, you can simply declare a module level array and push each emit into that array. Then, at any future time during that server execution, you can consult the array.
If, you want the emits stored across server invocations, then you would need to store them to some persistent storage (file, database, etc...).
// save incoming data from one particular message
var emitsForSomeMessage = [];
io.on("someMessage", function(data) {
// save the incoming data for future reference
emitsForSomeMessage.push(data);
});
Or, if you're trying to store all outgoing .emit() data, then you can override that method and save away what is sent.
var outgoingEmits = [];
(function() {
var oldEmit = io.emit;
io.emit = function(msg, data) {
outgoingEmits.push({msg: msg, data: data});
return oldEmit.apply(this, arguments);
};
})();
Since there are many different messages that may be sent or received, you can add your own logic to decide which messages are saved in the array or not.

Related

Socket.io response: How to get server to respond to multiple clients upon receiving info from all clients?

I'm learning about node.js, express, and socket.io
I've managed to successfully set up my server and connect to it from the browser using localhost:3000
I can send information from client to server and vice versa no problems.
What I now need to do is this.
Let's say we have 2 clients connected to the server.
Each client will send a piece of information.
socket.emit('mydata', datavariable);
On server:
socket.on('mydata', function(datavariable) {
myArray.push(datavariable); //myArray declared globally
});
How do I code it so the server will do something like this.
if(myArray.length = #ofconnectedclients) {
socket.emit("filledUpArray", myArray);
}
Or another way of saying it I guess is, once you have gotten a response from all clients connected, then do something.
I want to receive a piece of information from all clients, and after I have received that info from all clients, then send that data (now stored in an array) to all my clients.
Thanks!
So, it sounds like your problem statement is this:
When all connected clients have sent a certain piece of information to the server, then I want the server to do something.
First thing you have to do is keep track of all connected clients. You can either use socket.io's internal data structures for that or you can create your own. Here's creating your own on the server:
// map of all currently connected client objects
// socket is the key, custom object is the value
// methods are for managing data in the Map
let connectedSockets = new SocketMap();
// socket connects
io.on('connection', function(socket) {
let socketData = {};
socketData.socket = socket;
socketData.dataReceived = false;
// add it to our Map
connectedSockets.set(socket, socketData);
// monitor disconnects too
socket.on('disconnect', function() {
// remove it from the map
connectedSockets.delete(socket);
});
});
The second thing you need to do is to keep track of some per-socket state so you can initailize it at some point and then each time you receive a specific message from a client, you can check to see if all clients have sent the desired message. We'll create a couple functions to do that:
// this actually physically goes above the previous code, but I'm just placing it
// here for explanation purposes
class SocketMap extends Map {
// clear the dataReceived Boolean to false for all sockets
initReceived() {
for (let [key, value] of this) {
value.dataReceived = false;
}
}
// return Boolean if all sockets have dataReceived set to true
haveAllReceived() {
for (let [key, value] of this) {
if (!value.dataReceived) {
return false;
}
}
return true;
}
setReceived(socket) {
let data = this.get(socket);
if (!data) {
return false;
}
data.dataReceived = true;
}
}
Now you have methods on the connectedSockets Map object for keeping track of a dataReceived Boolean for each socket and for initializing them all to false and for setting one to true when a particular condition occurs (receiving a message) and for checking if they have all set the flag. So, you just have to determine when, in your code you want to call those messages.
Let's suppose that when the message you're waiting for from each socket is someMsg. So, you create a listener for that message and process it. We can incorporate that code into our previous connection handling code here:
// map of all currently connected client objects
// socket is the key, custom object is the value
// methods are for managing data in the Map
let connectedSockets = new SocketMap();
// socket connects
io.on('connection', function(socket) {
let socketData = {};
socketData.socket = socket;
socketData.dataReceived = false;
// add it to our Map
connectedSockets.set(socket, socketData);
// monitor disconnects too
socket.on('disconnect', function() {
// remove it from the map
connectedSockets.delete(socket);
});
// process this message
socket.on('someMsg', function(data) {
connectedSockets.setReceived(socket);
// if all sockets have now sent their data, then do something
if (connectedSockets.haveAllReceived()) {
// do your thing here
// reset state, ready for next time
connectedSockets.initReceived();
}
});
});

How to know if value changed in Parse Cloud Code afterSave hook?

I want to send push notifications every time the value of a single key of my object changes in a parse cloud code afterSave hook.
Parse.Cloud.afterSave("Channel", function(request) {
var channel = request.object
// TODO: check if value of channel key "state" was changed
});
How can I check if the value of the key state was updated?
This is all data I can get from the request object: http://parseplatform.org/Parse-SDK-JS/api/v1.11.0/Parse.Cloud.html#.TriggerRequest
The solution suggested in this thread feels wrong: Parse Javascript API Cloud Code afterSave with access to beforeSave values
I know I can do this via the dirty method in the beforeSave hook. However this does not work for me. Why? If I do send push notifications to many users this takes some time. The clients receiving the push notifications start requesting the updated channel object from the server. However they might receive an old version of the object because as long as beforeSave has not finished sending all pushes the channel object is not persisted in the database.
You can use request.original. For example:
Parse.Cloud.afterSave("Channel", function(request) {
var channel = request.object;
var channel_orig = request.original;
if (channel.get("status") != channel_orig.get("status")) {
// Send push notification.
}
});
The documentation states about request.original, that: "If set, the object, as currently stored." I'm not sure in what cases it would be set, though. In my use cases it works as provided in the code snippet above.

socket.io assigning custom socket.id

I keep track the list of every users connected in the array.
So if there is a new connection, it will check whether the user is already on the list or not, if he was already on the list, then assign their socket.id with the corresponding socket.id on the list, otherwise just add them to the list.
It's for preventing same user counted as 2 user while he attempt to do multi-login.
Object.keys(client).forEach(function (key) {
if (client[key].id == data.id){
is_connected = true;
socket.id = key;
}
});
I have no problem handling the messages/chat that was sent/received by the user who attempt multi-login.
socket.on('chat', function(msg){
var data = {"name": client[socket.id].name, "message": msg};
io.emit('chat', data);
});
The io.emit for the chat message was succesfully sent to the user who attempting multi-login.
The problem I got was whenever the user decide to logout/disconnect from the server.
io.emit('user_leave', client[socket.id].id);
[Multi-Login Case] -> Multi-User and Dual-User are same user attempting Multi-Login
Whenever the Main-User disconnected from the server, the Dual-User received 'user_leave' sent by the server, because io.emit supposed to send it to all sockets.
But not otherwise, while the Sub-User disconnected from the server, the Main-user do not receive 'user_leave' emitted by the server.
*Note: Main-User is login first, then the Dual-User. So the Main-User information was saved directly in the array, while the Sub-User socket.id was assigned with the Main-User socket.id
[Update]
B2 socket.id was assigned with B1 socket.id, the io.emit for chat work perfectly while io.emit for disconnect only emitted to All except Dual-User(B2)
socket.id is used internally by socket.io for its own socket list. You cannot overwrite that or you break some of its ability to maintain its own data structures.
You have two choices:
You can use the existing socket.id value as is (without overwriting it) so you don't break existing behavior. It is already guaranteed to be unique on the server.
You can use a different property name for your own id such as socket.userId and then you won't conflict.
If you need to, you can maintain a map between your own custom id and the socket.io socket.id so you could get to one from the other.
Similar question here: Socket.io custom client ID
generateId prop of io.engine object can be used for to set the custom id.
Using this way, the all socket ids can be created on the server side without any issue.
Actually I wrote an answer for a similar question today.
An example:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.engine.generateId = function (req) {
// generate a new custom id here
return 1
}
io.on('connection', function (socket) {
// listing the default namespace rooms
console.log("rooms: ", io.nsps["/"].adapter.rooms);
})
The console output would be as the following:
rooms: { '/#1': Room { sockets: { '/#1': true }, length: 1 } }
It seems to be it has been handled.
It must be in mind that socket id must be unpredictable and unique value with considering security and the app operations!
Extra: If socket.id is returned as undefined because of your intense processes on your generateId method, async/await combination can be used to overcome this issue on node.js version 7.6.0 and later. handshake method of node_modules/engine.io/lib/server.js file should be changed as following:
former:
Server.prototype.handshake = function (transportName, req) {
var id = this.generateId(req);
...
}
new:
Server.prototype.handshake = async function (transportName, req) {
var id = await this.generateId(req);
...
}

Double indexing in associative array

I'm building a server using Node.js. I store the objects representing my users in an associative array and right now the index of those objects are the sockets id of that connection. Using the Socket.io library i obtain it by simply doing socket.id, so for each handler that takes care of my requests I can always know which user made the request.
Client-side every user has the ids of the connected users (that are different from the socket ids).
The problem araises when i have an handler that is used to send a message from an user to another, i make an example:
User A needs to send a message to user B, my protocol specifies the message as something like this:
MSG_KEY:{MSG:'hello world',USER_ID:'12345'}
Server-side i have an handler that listens to "MSG_KEY" and when the message is sent it is executed and using the socket.id I can retrieve who made the request, but the problem is that i need to get also the user B but this time using his USER_ID. I don't want to use the socket.id to avoid session spoofing.
I first thought about indexing in my array the users by indexing them both from socket.id and user id.
My question is: is it a good idea to do this? Does socket.io provide a way to simplify this?
There's no particular reason you can't do that. I would use two separate objects (maps), and probably have the users be represented by objects rather than strings so that there was really only one user (referenced from two places).
E.g., a user:
var user = {userid: "JohnD", name: "John Doe"};
and then:
var usersByID = {};
usersByName[user.userid];
and
var usersBySocket = {};
usersBySocket[theSocketID] = user;
Or even
var users = {
byID: {},
bySocket: {}
};
users.byID[user.userid] = user;
users.bySocket[theSocketID] = user;

Javascript Web App Best Practices

I'm having a hard time writing my question succinctly and clearly, so let me describe what I'm working with.
I'm building a web application that:
has it's own API hosted on a subdomain (https://api.example.com)
has the main application hosted on the tld (https://www.example.com)
the tld doesn't have any database access, but instead interacts with the API to work with data
the tld authenticates with the api through OAuth and stores the access token and access token secret in a session
when the session ends, the access token is no longer used, thus logging the user out
I have a route in the tld (let's call it /ajax for this question) that the javascript calls (GET, PUT, POST, OR DELETE) to make requests to the api. This way, nobody ever has to see the access token, access token secret, consumer key, or consumer secret.
The way I see it, the access token and access token secret are really the only things I need to store in a session since I can grab everything else using the API, but instead of making a call every single time for every piece of data I need, I think some things should persist, like aspects of the user's profile, layout preferences, etc.
What is the best way for me to accomplish this? Local storage? Cookies? Should I scrap this and just store it in sessions?
And if you have some time, what other best practices are there for building sites like this that I may not know of?
You are on the right track I would say, but store your data in JavaScript primarily. And couple it with Local Storage when suitable.
When I build apps such as the one you are describing I usually take care to set up JavaScript representations of the data I receive via the API.
One such representation could look as follows below. Bear in mind that my example code below makes a couple of assumptions.
It makes the assumption that you have an api object defined which takes care of API calls, and invokes a callback on completion.
that the data returned by the API is JSON that simply can be assigned to a JavaScript variable,
That the JSON returned is a list of objects, each with an "id" field.
That you have some sort of event object, I usually build my own custom events that basically carry function objects as listeners and when fired go through the listeners and invoke the functions with or without a payload depending on the situation.
Data container example:
MYAPP.data.BaseContainer = function (api_url, loadedEvent) {
var self = {
// Array to store the data returned via the APIs
_data : [],
// The API URL used to fetch data
api_url : api_url,
// Boolean flag to signify whether the _data variable has been populated
is_loaded : false,
// The even to fire once _data has been populated
loadedEvent : loadedEvent,
/**
* Returns the state of the is_loaded variable
*/
loaded : function () {
return self.is_loaded;
},
/**
* Takes an ID and returns any member of the _data array
* that has that ID.
*
* #param id : an String or integer representing the ID.
* #returns {Object}
*/
byId : function (id) {
var toReturn = null;
for (var i = 0, len = self._data.length; i < len; i++) {
if (self._data[i].id == id) {
toReturn = self._data[i];
break;
}
}
return toReturn;
},
/**
* Returns the entire _data array.
*/
all : function () {
return self._data;
},
/**
* This simple callback just stores the json response in
* its entirety on the _data variable.
*/
callback : function(data) {
self._data = data;
self.is_loaded = true;
loadedEvent.fire(self._data);
},
/**
* Calls the API, if no callback has been specified as a parameter
* self.callback is used.
*/
getFromAPI : function(callback) {
if (typeof callback === 'undefined') {
callback = self.callback;
}
api.get(self.api_url, callback);
}
};
self.getFromAPI();
return self;
};
With this blueprint I can now create specific data containers like this:
/**
* Stores a list of "friends" gotten from the API.
* This is basically an instance of the BaseContainer object defined above.
*/
MYAPP.data.Friends = (function () {
var self = MYAPP.data.BaseContainer("API_URL_TO_FECTH_FRIENDS_LIST", FriendsLoadedEvent);
return {
byId : self.byId,
all : self.all,
loaded : self.loaded
};
}());
As soon as this code is run, an API call is made, and the FriendsLoadedEvent will be fired when it is done. So, to put it bluntly, I use JavaScript to store my stuff usually. But if you want to throw LocalStorage into the mix that is easy too!
Just add local storage code to the BaseContainer object that first detects whether the client actually supports localstorage, and if so mirror the _data field in local storage. This is handy to keep often used data quickly available between sessions. Use the readily available JSON parsing tools to convert the data from JSON to LocalStorage "text"and back.
Just keep in mind that you cannot rely on LocalStorage as your primary data structure, you have no guarantee that the client supports it, and even when it does the upper bounds for how much data you can actually store is different between the browsers. So use it to store data that:
You want access to very often,
that you feel should just be there, immediately as soon as the user logs in,
and that does not change often enough to warrant refreshing API calls constantly.
Congratulation! You've answered most of your question already. If you want to persist user data, you'll need to use something like local storage or cookies. In your case local storage is best. With cookies, each page request sends to cookies along in the header.
Best of Luck with your app.

Categories