Socket.io client side emits too many times to server side - javascript

Please help me figure out why my socket.io app fires too many emit messages.
Yes, I have spent weeks on the socket.io docs and relevant stackO questions, experimented with the nesting of my functions and everything but I am just totally stuck.
This is supposed to allow people in a 'room' to vote on an issue. Each client gets one vote (yea, nay, or abstain). The problem is when someone votes, it cannot just add one vote it adds a vote for the number of people in the socket. For example, if three people are in a room and the first vote is cast for 'yea' it registers 3x 'yea' votes. I cant get a good count ever!
client site:
var socket = io.connect('http://localhost');
const url = window.location.href;
let voteName = url.substring(url.lastIndexOf("/") + 1);
let notVoted = true;
$('.voteName').html(voteName.replace(/_/g, ' '));
function getRoom(){
return voteName;
}
socket.on('connect', () => {
// Connected, let's sign-up for to receive messages for this room
socket.emit('room', getRoom());
console.dir(socket.id);
});
function yea () {
if(notVoted){
socket.emit('yea',voteName);
console.log(voteName)
notVoted = false;
}
}
function nay () {
if(notVoted){
io.emit('nay',voteName);
notVoted = false;
}
}
function abs () {
if(notVoted){
io.emit('abs',voteName);
notVoted = false;
}
}
Server Side:
app.post('/votePick', (req, res) => {
let voteName = req.body.votePick;
res.send(voteName);
app.get('/'+voteName,(req,res)=>{
res.sendFile(__dirname + '/canvas.html');
});
var nsp = io.of('/');
io.sockets.on('connection', (socket) => {
socket.on('room', (voteName) => {
//TODO check if room exists
socket.join(voteName);
if(!db[voteName]) {
db[voteName]={'yea':0,'nay':0,'abs':0,'cnctCount':1};
io.emit('update',db[voteName]);
} else {
io.emit('update',db[voteName]);
}
});
socket.on('yea', (voteName)=>{
db[voteName].yea++;
io.emit('update',db[voteName]);
console.log(db[voteName].cnctCount);
});
socket.on('nay', (voteName)=>{
db[voteName].nay++;
io.emit('update',db[voteName]);
console.log(db[voteName]);
});
socket.on('abs', (voteName)=>{
db[voteName].abs++;
io.emit('update',db[voteName]);
console.log(db[voteName]);
});
});
});
my full repo is here:
https://github.com/Bokeefe/sockerPractice

You should do user validation. No one can prevent code on the clientside to be changed, so you always need to check on the serverside. For example you could store the socket ids that have already voted:
const voted = new Set;
then inside the socket event handlers:
if(voted.has(socket)) return;
voted.add(socket);
//vote
this should also solve the problem od votes counted twice.

Related

testing 2-player socket.io game. updating a label is affecting both pages

I don't understand why updating a label on one page is affecting the label on another page. I did not think the DOM was shared like that. Opening one tab or page successfully updates the label to 'player1', but when I open another tab/pg, it updates both labels to 'player2'.
<script>
var socket = io.connect('http://localhost:3000');
socket.on('connect', function() {
socket.emit('join');
socket.on('joinSuccess', function (playerSlot) {
if (playerSlot === 'player1') {
$("#playerID").text("you are player1");
} else if (playerSlot === 'player2') {
$("#playerID").text("you are player2");
}
}); //end joinSuccess
}); //end connect
I am merely trying to notify the user which player they are.
solution:
else if (playerSlot === 'player2') {
var elm = $("#playerID");
var empty = !elm.text().trim();
if (empty) {
elm.text("you are " + playerSlot);
}
}
Are you pushing the 'joinSuccess' message when new user joins? In such case this message will be passed to both the pages with same playerSlot value. So, all pages will be updated last joined player name.
In such case you can handle this with simple condition,
socket.on('joinSuccess', function (playerSlot) {
var elm = $("#playerID");
if (!elm.text().trim()) {
elm.text("you are " + playerSlot);
}
});

SOCKJS reply upon SOCKET_CREATED event

I am connection through Vertx eventbus (SockJS to my Java based backend. Everything work fine, However, I cannot find a way to send an initial message.
Is there a way to send back data when SockJS bridge receives SOCKET_CREATED to the sockjs browser side?
Thank you.
Taken from their documentation:
if (event.type() == SOCKET_CREATED || event.type() == SOCKET_CLOSED)
{
//...
vertx.eventBus().publish("fromServer", jmsg.toJSONString());
}
Your event instantiation may be different, but that would be how you check for the specific event and run code after it has occurred
You can check this code , where I'm using EventBus.
Here is the Reference code
this.eventBus = new EventBus(this.URL);
this.eventBus.onopen = (e) => {
this._opened = true;
console.log("open connection");
this.callHandlers('open', e);
this.eventBus.publish("http://localhost:8082", "USER LOGIN INFO");
this.eventBus.registerHandler("http://localhost:8081/pushNotification", function (error, message) {
console.log(message.body);
//$("<div title='Basic dialog'>Test message</div>").dialog();
});
}
this.eventBus.onclose = (e) => {
this.callHandlers('close', e);
}
}

SignalR changing hub subscription

I have a simple app, which displays a list of available signalR hubs. A user selects a hub and it connects to it, this subscribes an event to add messages to a table on the page. The user can then send messaged to that hub which will also fire the subscription adding that message to the table. This all works great.
Now if the user selects another hub, the app connects and sets up a new subscription, however the original subscription still fires causing duplicate messages to be added to the table. Each time the hub is changed further subscriptions get added causing one send to result in many messages in the table.
I have tried disconnecting the hub, disposing the hub and trying to remove the subscription with hubProxy.off(eventName), but nothing seems to work, other than a page reload.
Here is the code I have just added the onHub changed function as this is where everything is happening.
Any ideas appreciated. :)
function HubViewModel() {
var self = this;
self.hubConnection = '';
self.hub = '';
$.getScript("../signalR/hubs");
self.hubs = ko.observableArray();
self.selectedHub = ko.observable();
self.messageText = ko.observable();
self.messageCollection = ko.observableArray();
self.hubChanged = function () {
// Setup hub connection.
$.connection.hub.url = "../signalR";
self.hubConnection = $.hubConnection();
// Get the selected hub name.
var selectedHubName;
_.each(self.hubs(), function(item) {
if (item.hubId == self.selectedHub()) {
selectedHubName = item.hubName;
}
});
// Check for a selected connection
if (self.selectedHub()) {
// Create proxy.
self.hub = self.hubConnection.createHubProxy(selectedHubName);
// Remove any existing listener(s).
self.hub.off('addNewMessageToPage');
// Setup listener.
self.hub.On('addNewMessageToPage', function (sender, message) {
self.messageCollection().push({ hubName: selectedHubName, name: selectedHubName, message: message, dateTime: new Date().toLocaleString() });
$('#hubMessageGrid').dxDataGrid('instance').refresh();
});
// start connection.
self.hubConnection.start()
.done(function() {
toastr.success('hub connected');
$('#sendMessageButton').click(function() {
self.hub.invoke('sendAll', 'hub management page', self.messageText());
self.messageText('');
});
})
.fail(function(error) {
toastr.error('hub connection ' + error);
});
}
};
You can to disconnect the hub first by calling the self.hub.stop(); function
You need to pass the exact same handler instance when unsubscribing. Passing a different instance (even if the function body is the same) will not remove the handler.
See https://learn.microsoft.com/en-us/javascript/api/#microsoft/signalr/hubconnection?view=signalr-js-latest#off-string---args--any-------void-

How do I differentiate between a connection closing and a refresh in Meteor?

I am trying to determine when a user closes their connection. The problem is that, when I try to use this._session.socket.on("close",...) it also registers when the user refreshes.
Here is my code:
Meteor.publish("friends", function () {
var id = this._session.userId;
this._session.socket.on("close", Meteor.bindEnvironment(function()
{
// This logs when the user disconnects OR refreshes
console.log(id);
}, function(e){console.log(e)}))
return Meteor.users.find({...});
});
How do I differentiate between a refresh and a real disconnection?
EDIT: I would really like to avoid using a 'keep alive' function if possible.
Ok, this is a little bit hacky but I'm not sure if there is a better solution. This requires the mizzao:user-status package. The way I solved this problem is to call a meteor method inside the "on close" function that starts polling the database at 5 second intervals and checks if the user's status is online. After a set amount of time (I said 65 seconds), if the user has come online I know it was a refresh.
Anyway, the above is a little bit confusing, so here is the code:
//server
Meteor.publish("some_collection", function(){
var id = this._session.userId;
this._session.socket.on("close", Meteor.bindEnvironment(function(){
Meteor.call("connectionTest", id);
}, function(e){console.log(e)}));
return Meteor.users.find({..});
});
//Meteor method
Meteor.methods({
connectionTest: function(userId){
this.unblock();
var i = 0;
var stop = false;
var id = Meteor.setInterval(function(){
var online = Meteor.users.findOne(userId).status.online;
if(!online){
console.log("offline");
}
else{
stop = true;
console.log("still online");
}
i++;
if(stop || i > 12){
if(online){
//do something
}
else{
// do something else
}
Meteor.clearInterval(id);
}
}, 5000);
}
});

node.js/socket.io: Refresh table on reconnect?

I have node.js and socket.io performing real-time updates on a DataTables table. When a row is edited, other clients see the updates to the row without refreshing.
This all works well, but I'm at a loss regarding how to automatically refresh the table when node/socket reconnect to the server after a disconnect.
Currently, this is what happens:
Go to page containing table
Let device sleep/disconnect from
server
Power on device and see page containing table
socket.io reconnects to the server, but the table doesn't refresh to get the latest changes.
How can I get socket.io to refresh the table upon reconnect to the server? Ideally, the process would look like this:
Power on device
socket.io reconnects to server, triggers CSS "Loading..." overlay to prevent user from making changes to table
socket.io refreshes the table to show latest content
CSS "Loading..." overlay closes
Server-side script:
console.log('Starting the server');
var app = require('express')();
var express = require('express');
var fs = require("fs");
var server = require('https').createServer(SSL, app);
var io = require('socket.io')(server);
server.listen(100);
io.on('connection', function (socket) {
/*socket.emit('news', { hello: 'world' });
/socket.on('my other event', function (data) {
console.log(data);
});*/
});
io.set('authorization', function(handshakeData, accept) {
console.log('io.authorization called');
accept(null, true);
} );
var lockedRowIDs = [];
io.sockets.on('connection', function(socket) {
console.log('io.connection called');
socket.on('lock', function(rowID) {
console.log('Lock event for rowID: '+rowID);
lock(socket, rowID);
lockedRowIDs.push(rowID);
} );
socket.on('clientJoin', function(username) {
console.log('clientJoin event received');
socket.join('clients');
if (typeof lockedRowIDs !== 'undefined' && lockedRowIDs.length > 0) {
socket.emit('lockedRows', lockedRowIDs);
}
} );
socket.on('unlock', function(rowID) {
console.log('Unlock event for rowID: '+rowID);
unlock(socket, rowID);
removeItemFromArray(lockedRowIDs, rowID);
} );
socket.on('updateData', function(json, action, id) {
if (action == "edit" || action == "create") {
console.log('updateData event for rowID: '+json.row['DT_RowId']);
}
updateData(socket, json, action, id);
} );
} );
function lock(socket, rowID) {
socket.broadcast.to('clients').emit('lock', rowID);
setTimeout(function() {
io.sockets.in('clients').emit('timeout', rowID);
removeItemFromArray(lockedRowIDs, rowID);
},
180000);
}
function unlock(socket, rowID) {
socket.broadcast.to('clients').emit('unlock', rowID);
}
function removeItemFromArray(array, item) {
console.log('removeItemFromArray called with item: '+item);
for(var i = array.length - 1; i >= 0; i--) {
if(array[i] === item) {
array.splice(i, 1);
}
}
}
function updateData(socket, json, action, id) {
if (action == "edit" || action == "create") {
console.log('updateData called with rowID:'+json.row['DT_RowId']);
}
socket.broadcast.to('clients').emit('updateData', json, action, id);
}
Client-side script:
var socket = io('https://www.***.com:100');
socket.on('connect', function() {
console.log('Connected');
socket.emit('clientJoin');
} );
socket.on('lock', function(rowID) {
console.log('Lock event received for rowID: '+rowID);
row = $("tr[id='"+rowID+"']");
row.addClass('locked');
/* Pagenation fix Start */
var table = $('#example').DataTable();
table.row('#'+rowID).nodes().to$().addClass('locked');
/* Pagenation fix End */
} );
socket.on('unlock', function(rowID) {
console.log('Unlock event received for rowID: '+rowID);
row = $("tr[id='"+rowID+"']");
row.removeClass('locked');
/* Pagenation fix Start */
var table = $('#example').DataTable();
table.row('#'+rowID).nodes().to$().removeClass('locked');
/* Pagenation fix End */
} );
socket.on('timeout', function(rowID) {
console.log('Time out event received for rowID: '+rowID);
row = $("tr[id='"+rowID+"']");
row.removeClass('locked');
/* Pagenation fix Start */
var table = $('#example').DataTable();
table.row('#'+rowID).nodes().to$().removeClass('locked');
/* Pagenation fix End */
/* Check if the editor corresponds to the timed out rowID - start */
var modifier = editor.modifier();
if (modifier) {
var data = table.row(modifier).data();
console.log('rowID is '+data.DT_RowId);
if (data.DT_RowId == rowID) {
console.log('Timed out rowID: '+rowID+' matches Editor rowID: '+data.DT_RowId+'. Closing Editor now.');
editor.close();
}
else {
console.log('Timed out rowID: '+rowID+' does not match Editor rowID: '+data.DT_RowId+'. Keeping Editor open.');
}
}
/* Check if the editor corresponds to the timed out rowID - end */
} );
socket.on('lockedRows', function (rowIDs) {
console.log('Iterate through the list of rows and mark it as locked');
table = $('#example').DataTable();
rowCount = rowIDs.length;
console.log('Row count: '+rowCount);
for (var i=0; i<rowCount; i++) {
console.log(rowIDs[i]);
row = $("tr[id='"+rowIDs[i]+"']");
row.addClass('locked');
table.row('#'+rowIDs[i]).nodes().to$().addClass('locked');
}
} );
socket.on('updateData', function(json, action, id) {
if (action == "create" || action == "edit") {
var DT_RowId = json.row['DT_RowId'];
console.log('updateData socket event for rowID: '+DT_RowId+' and action: '+action);
}
var table = $('table#example').DataTable();
if (action == "edit") {
var editedRow = table.row('#'+DT_RowId).nodes().to$();
table.row(editedRow).data(json.row).draw();
console.log('Row updated');
}
if (action == "create") {
console.log('Row created');
table.row.add(json.row).draw();
}
if (action == "remove") {
var removedRow = table.row('#'+id).nodes().to$();
table.row(removedRow).remove().draw();
console.log('Row removed with id '+id);
}
} );
/* Ajax request has been completed, data retrieved from the server */
editor.on('postSubmit', function(e,json, data, action) {
console.log('Post submit');
console.log(data);
console.log('With JSON:');
console.log(json);
console.log('With action:');
console.log(action);
if (action == "create" || action== "edit") {
if (json.row){
console.log('rowID from JSON: '+json.row['DT_RowId']);
socket.emit('updateData', json, action);
}
}
if (action == "remove") {
console.log('rowID from JSON: '+data.id[0]);
socket.emit('updateData', null, action, data.id[0]);
}
} );
editor.on('close', function(e) {
console.log('Close event');
console.log(e);
var modifier = editor.modifier();
console.log(modifier)
if (modifier !== null) {
console.log('Inside modifier')
table = $('#example').DataTable();
if (table.row(modifier).node()) {
rowID = table.row(modifier).node().id;
console.log('rowID='+rowID);
row = $("tr[id='"+rowID+"']");
row.removeClass('locked');
table.row('#'+rowID).nodes().to$().removeClass('locked');
socket.emit('unlock', rowID);
}
}
} );
As I think you know, when you're connected, you're keeping the data up-to-date, but if you have a momentary disconnect and then reconnect, you might have missed some data updates.
There are a number of possible strategies for dealing with this.
Brute-force. Upon a reconnect, get a fresh copy of all the data as if the device had just been turned on. Less efficient, but easy to implement.
Transaction ID or Transaction Time. Each time the server sends an update, it sends either a transaction ID or a transaction server time with that update. The client then keeps track of the last transaction ID or transaction time that it received. When it does a reconnect, it sends an initial message with the last transaction ID or transaction time asking for any updates that have happened since that last transaction. The server can then look through its database to see if there are any newer transactions and, if so, send them to that client. This requires keep track of transactions at your database. It is common that the server can possibly return a "transaction id not supported" type of response which then forces the client back to a brute-force update from scratch. This is a back-stop in case a database rebuild or server crash causes older transaction values to get lost.
True Synchronization. The client reconnects and then does a true synchronization with the server where the client essentially says "this is what I have, do you have anything newer". In the interest of efficiency, many synchronization systems solve this problem by implementing the transaction id described in option 2, but there are certainly many other ways to do synchronization. True synchronization is more useful if the client might also be changing the data while it was disconnected.
As a simplifying way of implementing option 2, if your database does not already support the notion of journaled transactions, then some servers will implement a transaction log in memory where it keeps track of the last N hours of transactions. As long as the server stays up and a disconnect doesn't last longer than N hours, then a request for new data can be satisfied from the in memory transaction log (which is efficient). If the server restarts or the client has been gone longer than N hours, then the client is just forced to do a brute-force update, the same as the client would have done if it was powered off and then powered back on (losing all prior knowledge of the data).
Of course, if there are multiple users of the database, then the transaction log has to be implemented by the database itself in order to make sure it includes all possible transactions.

Categories

Resources