Node.js w/ Socket.io - switch users function (allow and prevent writing) - node.js

I'm building something with node.js and socket.io which allows users to write in a textarea (pretty much like a tchat), but I need them to write alternately. Something like:
User 1 is writing. User 2 and User 3 can't write.
User 1 send the message.
User 1 can't write. User 2 is allowed to write. User 3 can't write.
User 2 send the message.
User 1 and User 2 can't write. User 3 is allowed to write.
User 3 send the message.
User 1 is writing. User 2 and User 3 can't write.
... etc
For now, I have (on the client side) :
var ucan;
$('#txtform').submit(function(event){
if(ucan){
socket.emit('trigger', me);
ucan = false;
}
$('#txtArea').attr('readonly','true');
}
})
on the server side :
socket.on('trigger', function(user){
u = user.id + 1; // switch to next user since users[] (further)
// stores all the users with their ids
if(u >= users.length){
u = 0; // loop throug all users
}
io.sockets.socket( users[u] ).emit('turn');
})
which makes me on the client side again :
socket.on('turn', function(){
ucan = true;
$('#txtArea').removeAttr('readonly');
})
The problems are that when they connect on the app, new users have the permission to write, so the first round they can all write at the same time, and when they all have written, permission does not loop and nobody can write.
I thought maybe something exists inside node.js or socket.io which allows me to do this more simply (the way I did is probably not the best), or anything else, but since I'm a beginner and I found nothing on the web, I'm asking for your help.
Thank you !
p.s: please excuse my english it is not my first language :)

Have it readonly by default, then on the server side when a user connects, if they are the only socket connected, emit the 'turn'. You should probably also handle the case where the person whose turn it is disconnects, at which point you should do your trigger and let someone else have control.

Related

Tracking currently active users in node.js

I am building an application using node.js and socket.io. I would like to create a table of users who are actively browsing the site at any given moment, which will update dynamically.
I am setting a cookie to give each browser a unique ID, and have a mysql database of all users (whether online or not); however, I'm not sure how best to use these two pieces of information to determine who is, and who isn't, actively browsing right now.
The simplest way would seem to be to store the cookie & socket IDs in an array, but I have read that global variables (which presumably this would have to be) are generally bad, and to be avoided.
Alternatively I could create a new database table, where IDs are inserted and deleted when a socket connects/disconnects; but I'm not sure whether this would be overkill.
Is one of these methods any better than the other, or is there a way of tracking this information which I haven't thought of yet?
You can keep track of active users in memory without it being a global variable. It can simply be a module level variable. This is one of the advantages of the nodejs module system.
The reasons to put it in a database instead of memory are:
You have multiple servers so you need a centralized place to put the data
You want the data stored persistently so if the server is restarted (normally or abnormally) you will have the recent data
The reasons for not putting it directly in a database:
It's a significant load of new database operations since you have to update the data on every single incoming request.
You can sometimes get the persistence without directly using a database by logging the access to a log file and then running chron jobs that parse the logs and do bulk addition of data to the database. This has a downside in that it's not as easy to query live data (since the most recent data is sitting in databases and hasn't been parsed yet).
For an in-memory store, you could do something like this:
// middleware that keeps track of user access
let userAccessMap = new Map();
app.use((req, res, next) => {
// get userId from the cookie (substitute your own cookie logic here)
let id = id: req.cookie.userID;
let lastAccess = Date.now();
// if you want to keep track of more than just lastAccess,
// you can store an object of data here instead of just the lastAccess time
// To update it, you would get the previous object, update some properties
// in it, and then set it back in the userAccessMap
userAccessMap.set(id, lastAccess);
next();
});
// routinely clean up the userAccessMap to remove old access times
// so it doesn't just grow forever
const cleanupFrequency = 30 * 60 * 1000; // run cleanup every 30 minutes
const cleanupTarget = 24 * 60 * 60 * 1000; // clean out users who haven't been here in the last day
setInterval(() => {
let now = Date.now();
for (let [id, lastAccess] of userAccessMap.entries()) {
if (now - lastAccess > cleanupTarget) {
// delete users who haven't been here in a long time
userAccessMap.delete(id);
}
}
}, cleanupFrequncy);
// Then, create some sort of adminstrative interface (probably with some sort of access protection)
// that gives you access to the user access info
// This might even be available in a separate web server on a separate port that isn't open to the general publoic
app.get("/userAccessData", (req, res) => {
// perhaps convert this to a human readable user name by looking up the user id
// also may want to sort the data by recentAccess
res.json(Array.from(userAccessMap));
});

Change Socket for another user

I'm trying to develop an API for multiplayer online using socket programming in node js
I have some basic questions:
1. How to know which connection is related to a user?
2. How to create a socket object related to another person?
3. When it's opponent turn, how to make an event?
4. There is a limited time for move, how to handle the time to create an event and change turn?
As it is obvious I don't know how to handle users and for example list online users
If you can suggest some articles or answering these questions would be greate
Thanks
Keep some sort of data structure in memory where you are saving your sockets to. You may want to wrap the node.js socket in your own object which contains an id property. Then you can save these objects into a data structure saved in memory.
class User {
constructor(socket) {
this.socket = socket;
this.id = //some random id or even counter?
}
}
Then save this object in memory when you get a new socket.
const sockets = {}
server = net.createServer((socket) => {
const user = new User(socket);
sockets[user.id] = user
})
I am unsure what you mean by that, but maybe the above point helps out
This depends on when you define a new turn starts. Does the new turn start by something that is triggered by another user? If so use your solution to point 2 to relay that message to the related user and write something back to that socket.
Use a timeout. Maybe give your User class an additional property timeout whenver you want to start a new timeout do timeout = setTimeout(timeouthandler,howlong) If the timeouthandler is triggered the user is out of time, so write to the socket. Don't forget to cancel your timeouts if you need to.
Also, as a side note, if you are doing this with pure node.js tcp sockets you need to come up with some ad-hoc protocol. Here is why:
socket.on("data", (data) => {
//this could be triggered multiple times for a single socket.write() due to the streaming nature of tcp
})
You could do something like
class User {
constructor(socket) {
this.socket = socket;
this.id = //some random id or even counter?
socket.on("data", (data) => {
//on each message you get, find out the type of message
//which could be anything you define. Is it a login?
// End of turn?
// logout?
})
}
}
EDIT: This is not something that scales well. This is just to give you an idea on what can be done. Imagine for some reason you decide to have one node.js server instance running for hundreds of users. All those users socket instances would be stored in the servers memory

Discord <#!userid> vs <#userid>

so I'm creating a bot using Node.JS / Discord.JS and I have a question.
On some servers, when you mention a user, it returns in the console as <#!userid> and on other it returns as <#userid>.
My bot has a simple points / level system, and it saves in a JSON file as <#!userid>, so on some servers when trying to look at a users points by mentioning them will work, and on others it won't.
Does anyone have any idea how to fix this? I've tried to find an answer many times, and I don't want to have it save twice, once as <#!userid> and then <#userid>. If this is the only way to fix it then I understand.
Thanks for your help!
The exclamation mark in the <#!userID> means they have a nickname set in that server. Using it without the exclamation mark is more reliable as it works anywhere. Furthermore, you should save users with their id, not the whole mention (the "<#userid>"). Parse out the extra symbols using regex.
var user = "<#!123456789>" //Just assuming that's their user id.
var userID = user.replace(/[<#!>]/g, '');
Which would give us 123456789. Their user id. Of course, you can easily obtain the user object (you most likely would to get their username) in two ways, if they're in the server where you're using the command, you can just
var member = message.guild.member(userID);
OR if they're not in the server and you still want to access their user object, then;
client.fetchUser(userID)
.then(user => {
//Do some stuff with the user object.
}, rejection => {
//Handle the error in case one happens (that is, it could not find the user.)
});
You can ALSO simply access the member object directly from the tag (if they tagged them in the message).
var member = message.mentions.members.first();
And just like that, without any regex, you can get the full member object and save their id.
var memberID = member.id;

[Node.JS Socket.io]communication between two targeted sockets

I'm currently working on a small node.js game.
Game supposedly has a global chat, with a "logged in" list to challenge people.
For the chat and the logged in list, i'm using the default socket.io room/namespace.
I successfully send the challenge request with following code
// When a user sends a battle challenge
socket.on("sendChallenge", function(data) {
// Try with room
socket.join('battleRoom');
var data= {
"userID": data.targetID,
"challengerName": data.challengerName
};
console.log(data.challengerName + " challenged " + data.userID);
// broadcast the message, but only the concerned player will answer thanks to his ID
socket.broadcast.emit('receiveChallenge', data);
});
Client side, I then have this code :
socket.on('receiveChallenge', function (data) {
if (data.userID == userID) {
alert("received challenge from " + data.challengerName);
socket.emit('ack');
}
});
The right player indeed receive the alert, and sends 'ack' to the server :
socket.on('ack', function() {
socket.join('battleRoom');
socket.to('battleRoom').broadcast.emit('receiveMessage', 'SYSTEM: Battle begun');
//socket.to('battleRoom').emit('receiveMessage', 'SYSTEM: Battle begun');
})
Except that the "socket.to('battleRoom').broadcast.emit('receiveMessage', 'SYSTEM: Battle begun');" is only received by the challenger and not by the challenged, and I'm stuck.
(the 2nd line is commented because challenger received 2 messages and challenged received none)
The way i understand it, on the server, the functions socket.on() have "socket" as the client that sent the message, and then you use broadcast to send to all others.
Why is why, in the sendChallenge event, i have the socket.join('battleRoom') for the challenger to enter battleRoom.
I then broadcast the challenge, and asks the client to acknowledge the challenge.
In the ack, the client then supposedly joins battleRoom too.
But i'm obviously doing something wrong, and i can't seem to see what...
I want both the challenger and the challenged communicating through the server.
A link to an image more or less showing the situation : Here
(screen is during the second time i clicked on the quickFight button, showing the Battle begun of the 1st click on the left, and the alert that user was challenged on the right)
Thanks in advance for your help!
As per the socket.io documentation broadcast does the following:
Broadcasting messages
To broadcast, simply add a broadcast flag to emit and send method calls. Broadcasting means sending a message to everyone else except for the socket that starts it.
So when you do:
socket.to('battleRoom').broadcast.emit('receiveMessage', 'SYSTEM: Battle begun');
You do not emit to the socket that broadcasted to the room. Simply replace that line with the following:
io.to('battleRoom').emit('receiveMessage', 'SYSTEM: Battle begun');
This is assuming that you named your socket.io object io, if you named it differently use that.
Just as a side-note, it's not necessary here to broadcast your original message to everyone if you create an associative array with all your connected socket id's (or sockets themselves). Then you can find the challenged socket id in your array and only emit to them specifically with the challenge. This would cut down on your server calls.

Issue with dialing REGISTERED (but offline) users

i'm facing the following scenario:
we have to local (REGISTERED) users (iOS apps pjSIP) which initiating local calls between each other.
the problem arise when one of the users (let's say user B) is closing the application few minutes after he successfully REGISTERS.
now, when user A tries to call user B we see that the INVITE is sent but we got no reply (e.g 180 ringing) from User B.
Note: when we are sending an invite to User B he get's Push notification to his device what cases him to open the app (and to Re-REGISTER)
our targets are:
1. determinate if user B (e.g the callee) is reachable before we are sending an INVITE in cases User B App is closed and his extension is still REGISTERED
2. be able to send invite to user B right after he REGISTER
we tried to solve this issue from many directions:
1.Qualify - tried to decrease the qualify time of the registersion period so user B will be UNAVAILABLE as soon as possible (and we will check the device state before we will dial) but it may cause to massive OPTIONS on our network, and it's not going to solve target #2
2.AMI Service - it can catch events like: User A Dials user B , User B is Ringing (180 Ringing) and save those statuses to ASTDB . all this logic will be prefomed before we will launch the dial.
this solution is clumsy and to cmplicated and it requires to watch yet another service
after some research i'v got to the conclusion that the most suitable solution will be to store the time of the last OPTIONS reply of each extension(requires a patch in chan_sip.c) . re-trigger sip options qualify request to user B before User A Dials . if the original value (e.g before we re-triggered the OPTIONS) is equal the value after we trigged the OPTIONS it means that User B has not replied OPTIONS.
i'm attaching the changes i'v preformed to complete this task.
i would like to know if solution for the issue is suitable and valid and of course if there is a better way to preform it.
These is the change in chan_sip (using asterisk 11.7)
i'v prefomed changes only on the following lines:
23492 to 23500
23485 /*! \brief Handle qualification responses (OPTIONS) */
23486 static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_request *req)
23487 {
23488 struct sip_peer *peer = /* sip_ref_peer( */ p->relatedpeer /* , "bump refcount on p, as it is being used in this function(handle_response_peerpoke)")*/ ; /* hope this is already refcounted! */
23489 int statechanged, is_reachable, was_reachable;
23490 int pingtime = ast_tvdiff_ms(ast_tvnow(), peer->ps);
23491
23492 time_t result = time(NULL);
23493 result = (int)result;
This is how should implement dialplan with the patch:
Dial(SIP/${dest},10,Rgb(check_extension,s,1));
I'm sending the call to context before inviting ( with the "b" option)
context check_extension {
s => {
Set(IS_REACHABLE=0);
Verbose(KOLA/LastQualify/${DEST}); // Display User B initial quliafy
Set(INITIAL_QUALIFY=${DB(KOLA/LastQualify/${DEST})}); // Store it
for(loop=0;${loop}<60;dialLoop=${loop}+1) { // Loop untill the qualify will be changes
System(/usr/sbin/asterisk -rx "sip qualify peer ${DEST}");
Wait(2); // we need to wait a while for a response
Set(LAST_QUALIFY=${DB(KOLA/LastQualify/${DEST})}); // set the new qualify
if (${LAST_QUALIFY} > ${INITIAL_QUALIFY}) { // if the new qualify is newer, User B is reachable
Set(IS_REACHABLE=1);
break;
}
}
if (${IS_REACHABLE} = 0) {
Verbose(Peer is not reachable);
Hangup();
}
}
}
Best option for that is not use asterisk.
Use kamailio or opensips project, it can handle thousands of options packets.
Also you HAVE rewrite your app so when it closed it UNREGISTER, as that described in sip RFC.
To summarize: you are using buggy application, and triing do on asterisk thing it not designed to(large amount of users with options). So correct answer - use correct tools for this task.

Resources