node.js sockets remembering previous state instead of current one? - javascript

I'm trying to write an application that waits until there are two free users, then matches them together, in order to do this I am using the following function:
var gameManager;
io.on('connection', function(socket){
connected_users.push(socket.id);
console.log('user connected: ', socket.id);
if (connected_users.length >= 2) {
if (!gameManager) {
var gameManager = require('./scripts/gameManager.js')();
console.log('game started');
}
io.emit('initialize', gameManager.getFen());
}
console.log('appending event listener to user');
socket.on('move', function(move_json) {
console.log('move detected', move_json.player_id, gameManager)
if(!gameManager) {
return
}
// DO SOMETHING //
});
});
It seems to work fine, when the second user connects immediately both games initialize (I know that right now I'm emitting the initialize to all clients, this will be changed in the future), but then the 'move' emits from the first client always fail while the ones from the second work.
That is, suppose A connected first and then B, when A does the event that triggers an emit, the log line above shows
move detected [id of player A] undefined
while doing a move on board B gives
move detected [id of player B] GameManager {/* all the proterties*/}
Isn't it supposed to be the same exact object? why is the first socket unable to find it while the second is working as expected?

Related

Remove only one of socket.io event handlers

I want to write an application that does the following:
Starts a counter starting from 00:00, and increases time by 30 minutes around every passing second
Clients can connect to the server and check the time that it currently has
Clients can press press a button that increases the time by an additional 30 minutes
I want to build on it further, this is why I'm using Socket.io, but for now I just want to accomplish these three things. I have the following code after server initialization:
const io = socketio(server)
io.on('connection', (socket) => {
logger.info(`There is a new connection! ${socket.id}`)
// Initialize time
initializeTime(socket)
// Start clock
startClock(io)
// Remove all handlers from 'connection'
io.removeAllListeners('connection', startClock)
// Increase time if user presses button
socket.on('increaseTime', increaseTime)
})
function initializeTime(socket) {
socket.emit('time', `${currentTime.utc().format('HH:mm')}`)
logger.info(`Clock set up to ${currentTime.utc().format('HH:mm')}`)
}
function startClock(io) {
setInterval(() => {
currentTime.add(30, 'minutes')
io.emit('time', `${currentTime.utc().format('HH:mm')}`)
logger.info(`Clock updated to ${currentTime.utc().format('HH:mm')}`)
}, 1250)
}
function increaseTime() {
currentTime.add(30, 'minutes')
this.emit('time', `${currentTime.utc().format('HH:mm')}`)
logger.info('Key pressed on client!')
}
With this implementation, only the user that was first connected can manually, additionally increase the time on the server. First, I tried to remove only the starClock function from the 'connection' event, but then I realized that I probably can't do that, I can only remove the handler that was passed as a callback argument. This is why I wrote 'removeAllListeners', to show that I (probably) understand that what I'm trying to do is not the right way, and with this approach I would remove everything else that I specified, and this is why only the first client could increase the time.
Then I tried something else, that works perfectly, but still seems like bad code:
io.on('connection', function iNeedThis(socket) {
logger.info(`There is a new connection! ${socket.id}`)
// Initialize time
initializeTime(socket)
// Increase time if user presses button
socket.on('increaseTime', increaseTime)
})
io.on('connection', function iDoNotNeedThis(socket) {
// Start clock
startClock(io)
io.removeListener('connection', iDoNotNeedThis)
})
What would be the solution to make sure that startClock runs on first connection, but only runs once, while everything else runs on all the connections. I feel like I'm missing something conceptually. Thank you!

Is there any way to determine if a nodejs childprocess wants input or is just sending feedback?

I had a little freetime so I decided to rewrite all my bash scripts in JavaScript (NodeJS - ES6) with child processes. Everything went smoothly until I wanted to automate user input.
Yes, you can do automate the user input. But there is one Problem - you can't determine if the given data event is a feedback or a request for input. At least I can't find a way to do it.
So basically you can do this:
// new Spawn.
let spawn = require('child_process');
// new ufw process.
let ufw = spawn('ufw', ['enable']);
// Use defined input.
ufw.stdin.setEncoding('utf-8');
ufw.stdout.pipe(process.stdout);
ufw.stdin.write('y\n');
// Event Standard Out.
ufw.stdout.on('data', (data) => {
console.log(data.toString('utf8'));
});
// Event Standard Error.
ufw.stderr.on('data', (err) => {
// Logerror.
console.log(err);
});
// When job is finished (with or without error) it ends up here.
ufw.on('close', (code) => {
// Check if there were errors.
if (code !== 0) console.log('Exited with code: ' + code.toString());
// End input stream.
ufw.stdin.end();
});
The above example works totally fine. But there are 2 things giving me an headache:
Will ufw.stdin.write('y\n'); wait until it is needed and what happens if I have multiple inputs? For example 'yes', 'yes', 'no'. Do I have to write 3 lines of stdin.write()?
Isn't the position where I use ufw.stdin.write('y\n'); a little confusing? I thought I need the input after my prompt made a request for input so I decided to change my code that my stdin.write() could run at the right time, makes sense right? However the only way to check when the 'right' time is on the stdout.on('data', callback) event. That makes thinks a little difficult, since I need to know if the prompt is aksing for user input or not...
Here is my code which I think is totally wrong:
// new Spawn.
let spawn = require('child_process');
// new ufw process.
let ufw = spawn('ufw', ['enable']);
// Event Standard Out.
ufw.stdout.on('data', (data) => {
console.log(data.toString('utf8'));
// Use defined input.
ufw.stdin.setEncoding('utf-8');
ufw.stdout.pipe(process.stdout);
ufw.stdin.write('y\n');
});
// Event Standard Error.
ufw.stderr.on('data', (err) => {
// Logerror.
console.log(err);
});
// When job is finished (with or without error) it ends up here.
ufw.on('close', (code) => {
// Check if there were errors.
if (code !== 0) console.log('Exited with code: ' + code.toString());
// End input stream.
ufw.stdin.end();
});
My major misunderstanding is when to use stdin for user input (automated) and where to place it in my code so it will be used at the right time, for example if I have multiple inputs for something like mysql_secure_installation.
So I was wondering if it is possible and it seems not. I posted an issue for node which ended up beeing closed: https://github.com/nodejs/node/issues/16214
I am asking for a way to determine if the current process is waiting for an input.
There isn't one. I think you have wrong expectations about pipe I/O
because that's simply not how it works.
Talking about expectations, check out expect. There is probably a
node.js port if you look around.
I'll close this out because it's not implementable as a feature, and
as a question nodejs/help is the more appropriate place.
So if anyone has the same problem as I had you can simply write multiple lines into stdin and use that as predefined values. Keep in mind that will eventually break the stream if any input is broken or wrong in feature updates:
// new Spawn.
let spawn = require('child_process');
// new msqlsec process.
let msqlsec = spawn('mysql_secure_installation', ['']);
// Arguments as Array.
let inputArgs = ['password', 'n', 'y', 'y', 'y', 'y'];
// Set correct encodings for logging.
msqlsec.stdin.setEncoding('utf-8');
msqlsec.stdout.setEncoding('utf-8');
msqlsec.stderr.setEncoding('utf-8');
// Use defined input and write line for each of them.
for (let a = 0; a < inputArgs.length; a++) {
msqlsec.stdin.write(inputArgs[a] + '\n');
}
// Event Standard Out.
msqlsec.stdout.on('data', (data) => {
console.log(data.toString('utf8'));
});
// Event Standard Error.
msqlsec.stderr.on('data', (err) => {
// Logerror.
console.log(err);
});
// When job is finished (with or without error) it ends up here.
msqlsec.on('close', (code) => {
// Check if there were errors.
if (code !== 0) console.log('Exited with code: ' + code.toString());
// close input to writeable stream.
msqlsec.stdin.end();
});
For the sake of completeness if someone wants to fill the user input manually you can simply start the given process like this:
// new msqlsec process.
let msqlsec = spawn('mysql_secure_installation', [''], { stdio: 'inherit', shell: true });

socket.io stop re-emitting event after x seconds/first failed attempt to get a response

I noticed that whenever my server is offline, and i switch it back online, it receives a ton of socket events, that have been fired while server was down. ( events that are ... by now outdated ).
Is there a way to stop socket.io from re-emitting the events after they have not received a response for x seconds ?.
When all else fails with open source libraries, you go study the code and see what you can figure out. After spending some time doing that with the socket.io source code...
The crux of the issue seems to be this code that is here in socket.emit():
if (this.connected) {
this.packet(packet);
} else {
this.sendBuffer.push(packet);
}
If the socket is not connected, all data sent via .emit() is buffered in the sendBuffer. Then, when the socket connects again, we see this:
Socket.prototype.onconnect = function(){
this.connected = true;
this.disconnected = false;
this.emit('connect');
this.emitBuffered();
};
Socket.prototype.emitBuffered = function(){
var i;
for (i = 0; i < this.receiveBuffer.length; i++) {
emit.apply(this, this.receiveBuffer[i]);
}
this.receiveBuffer = [];
for (i = 0; i < this.sendBuffer.length; i++) {
this.packet(this.sendBuffer[i]);
}
this.sendBuffer = [];
};
So, this fully explains why it buffers all data sent while the connection is down and then sends it all upon reconnect.
Now, as to how to prevent it from sending this buffered data, here's a theory that I will try to test later tonight when I have more time.
Two things look like they present an opportunity. The socket notifies of the connect event before it sends the buffered data and the sendBuffer is a public property of the socket. So, it looks like you can just do this in the client code (clear the buffer upon connect):
// clear previously buffered data when reconnecting
socket.on('connect', function() {
socket.sendBuffer = [];
});
I just tested it, and it works just fine. I have a client socket that sends an increasing counter message to the server every second. I take the server down for 5 seconds, then when I bring the server back up before adding this code, all the queued up messages arrive on the server. No counts are missed.
When, I then add the three lines of code above, any messages sent while the server is down are not sent to the server (technically, they are cleared from the send buffer before being sent). It works.
FYI, another possibility would be to just not call .emit() when the socket is not connected. So, you could just create your own function or method that would only try to .emit() when the socket is actually connected, thus nothing would ever get into the sendBuffer.
Socket.prototype.emitWhenConnected = function(msg, data) {
if (this.connected) {
return this.emit(msg, data);
} else {
// do nothing?
return this;
}
}
Or, more dangerously, you could override .emit() to make it work this way (not my recommendation).
Volatile events are events that will not be sent if the underlying connection is not ready (a bit like UDP, in terms of reliability).
https://socket.io/docs/v4/emitting-events/#volatile-events
socket.volatile.emit("hello", "might or might not be received");

Socket.io: receive event triggering multiple times

I'm trying to implement a simple lock using the following code:
Server:
socket.on('lock', function(ressInfo)
{
var lock = false;
if(ressLocks.indexOf(ressInfo.id) === -1)
{
ressLocks.push(ressInfo.id);
lock = true;
}
socket.emit("lock", lock);
});
Client:
this.requestLock = function(ressInfo, callback)
{
if(currentlyOnline)
{
socket.emit("lock", ressInfo);
socket.on("lock", function(lock)
{
// this gets triggered one additional time with each lock
callback(lock);
});
}
else
{
callback(true);
}
}
On the first call I get one callback with true, on the second call I get two callbacks with false, on the third call three, etc.
What is happening here? Why does socket.on get called multiple times?
I can think of two reasons:
Client side:
If you are registering for lock function multiple time
Server side:
As i assume this happens if you are registering multiple times to listen to the events assuming that you are running the socket.on function everytime you recieve events
Heres how you can debug.
Install node-debug
do node-debug "filename"
This will open in debug mode. Now check how many time the socket.on for 'lock' is being registered. I had similar issue. Heres how i solved it.
//check if msgtype is already binded
if (!isMessageTypeBinded(msgType, someKey)) {
// emit ot connection
io.of(someKey).emit(msgType, message)
return
} else {
// apply filter to all connection
var sockets = io.of(someKey).sockets
if (msgType) { // check for message tye
activeMsgConn.push({
"msgType": msgType,
"accessKey": someKey
})
for (index in sockets) {
var socket = sockets[index]
socket.on(msgType, notifyNewMsg(socket))
io.of(someKey).emit(msgType, message)
}
}
}
I am maintianing an array of all the connections made till now.
If a new msg comes, i'll first check if that msg was already binded to the socket and dont add any new namespace. Else i loop thorugh all the socket conneciton and add this handler.
In your case the code need not be same but implementation can be similar.

Sending mouse clicks to a differnet computer using webrtc

I am trying to connect computer "a" to computer "b" using webrtc and print out the "Click" on computer "b" when the mouse is clicked on computer "a"'s canvas. I already created a working webrtc example where I make a connection between computer "a" and "b" and send messages between them using textboxes(chat).
I know to Attach a click event to the document. When the user clicks anywhere in the document, output "Click" will be displayed.
document.addEventListener("click", function(){
message.value= "Click!";
});
And these are some of the webrtc functions I have, I didnt post all my webRTC functions because I dont wanna make the question longer, it already is.
// a nice wrapper to send data
function send (room, key, data) {
roomRef.child(room).child(key).set(data);
}
// wrapper function to receive data
function recv (room, type, cb) {
roomRef.child(room).child(type).on("value", function (snapshot, key) {
var data = snapshot.val();
if (data) { cb(data); }
});
}
// get references to the document tags
var chatlog = document.getElementById("chatlog");
var message = document.getElementById("message");
function bindEvents () {
channel.onopen = function () { console.log("Channel Open"); }
channel.onmessage = function (e) {
// add the message to the chat log
chatlog.innerHTML += "<div>Peer says: " + e.data + "</div>";
};
}
// send a message the textbox throught
// the data channel for a chat program
function sendMessage () {
var msg = message.value;
channel.send(msg);
message.value = "";
}
My question is I dont know how to connect these two codes together or even if i did, I am not sure if it would work. So my question is how can I click on the canvas on computer "a" and get the textbox to print out "Click" on computer "b".
Thanks for reading
You could look into node.js and socket.io.
With these two you could connect multiple clients together and have a real-time communication between them. Other alternative is to use ajax with php, and make one browser to poll for new commands from server, and other browser to send them to server.
You're mostly there. What you can do is, after setting the message.value property, is call the sendMessage() function. This should trigger the application to send the correct value through the WebRTC Connection.

Categories

Resources