-1

I am working on a browser-game in Node.Js and I have this script :

game.js >>

var config = require('./game_config.js');
var mysql = require('mysql');
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var connexion = mysql.createConnection({
    'host': config.DB_HOST,
    'user' : config.DB_USER,
    'password' : config.DB_PASS,
    'database' : config.DB_NAME 
});
var Player = require('./server/class.player.js');

io.on('connect', function(socket) {
    console.log('Co');
    var player
    socket.on('login', function(data) {
        connexion.query("SELECT * FROM player WHERE nick = '"+data.login+"' AND pass = '"+data.pass+"'", function(err, rows) {
            if (err) {
                throw err;
            } else {
                if (rows.length == 0) {
                    var dataRet = "LOG";
                    socket.emit('login', dataRet);
                } else {
                    var p = rows[0];
                    var dataRet = new Player(p.id, p.nick, p.map_id, p.x, p.y, connexion).toJson();
                    console.log(dataRet);
                }
                // Without setTimeout it wouldn't work because the object didn't have the time to instantiate
                setTimeout(function() {
                    socket.emit('login', dataRet);
                },1000);
            }
        });
    });
    socket.on('disconnect', function(socket) {
        console.log('Disco');
    });
});

class.Player.js >>

var Player = function (id, name, map_id, x, y, connexion) {
    this.id = id;
    this.name = name;
    this.map_id = map_id ;
    this.x = x;
    this.y = y;
    this.link = connexion;
    this.toJson = function () {
        return {
            'id' : this.id,
            'name' : this.name,
            'map_id' : this.map_id,
            'x' : this.x,
            'y' : this.y
        };
    }
}

module.exports = User;

So basicly, my code works fine thanks to the "setTimeout()" in game.js (for the socket.emit() event). If I don't use it, the object 'dataRet' doesn't have the time to instantiate due to the asynchronousity of Node.js, so the socket emits "undefined" or "null".

So I was thinking, there MUST be a way to listen for an object instantiation in order to emit it through socket.io as soon as it's done.

Mouradif
  • 2,666
  • 1
  • 20
  • 37
  • There's nothing in your `Player` constructor that's asynchronous. `var dataRet = new Player(...)` will populate `dataRet` **immediately**. Whatever you observed that made you think you needed the `setTimeout` for that is not due to that, but due to something else. – T.J. Crowder Dec 17 '14 at 13:09
  • Side note: Your code is **wide open** to SQL Injection. More: http://bobby-tables.com/ – T.J. Crowder Dec 17 '14 at 13:11
  • And you're calling `socket.emit('login', dataRet);` twice in the case of no matching rows. – T.J. Crowder Dec 17 '14 at 13:21
  • yes @T.J.Crowder true, I should remove the 'emit' in the first "if" statement. For the asynchronicity, if I don't do "setTimeout()", it just returns null. And thank you for the bobby-tables link ;) – Mouradif Dec 17 '14 at 13:44
  • @ Ki: As I said above: There's nothing asynchronous in your `Player` constructor. Look closer at the problem, because it's not that `new Player` is magically becoming asynchronous. – T.J. Crowder Dec 17 '14 at 13:52

1 Answers1

0

Warning: SQL Injection Vulnerability

This is not related your question per se but this is quite important - you have a huge SQL injection vulnerability and anyone can do anything to your database.

Instead of:

connection.query(
  "SELECT * FROM player WHERE nick = '"
  + data.login + "' AND pass = '" + data.pass + "'",
  function (err, rows) {
    //...
  }
);

either use:

connection.escape(data.login) and connection.escape(data.pass) in place of data.login and data.pass

or:

connection.query(
  "SELECT * FROM player WHERE nick = ? AND pass = ?", [data.login, data.pass],
  function (err, rows) {
    // ...
  }
);

It is not only safer but also actually much easier to read and understand. See: Escaping query values in node-mysql manual.

The Answer

Now, back to your question. There is nothing asynchronous about your Player constructor so your problem must be something else. What is strange here us that your Player.js exports User (that is not defined) and not Player (that is defined) so I'm surprised it even works at all. Or you maybe posted a different code than what you're actually using which would explain why you have a race condition that is not obvious from the code.

But if your Player constructor was making some asynchronous calls then I would suggest adding a callback argument and calling it from the constructor:

var Player = function (id, name, map_id, x, y, connexion, callback) {
    this.id = id;
    this.name = name;
    this.map_id = map_id ;
    this.x = x;
    this.y = y;
    this.link = connexion;
    this.toJson = function () {
        return {
            'id' : this.id,
            'name' : this.name,
            'map_id' : this.map_id,
            'x' : this.x,
            'y' : this.y
        };
    }

    // some async call that you have to wait for
    // symbolized with setTimeout:
    setTimeout(function () {
      if (callback && typeof callback === 'function') {
        callback(this);
      }
    }, 1000);
}

and then you can pass a callback to your constructor, so this:

          } else {
                var p = rows[0];
                var dataRet = new Player(p.id, p.nick, p.map_id, p.x, p.y, connexion).toJson();
                console.log(dataRet);
            }
            // Without setTimeout it wouldn't work because the object didn't have the time to instantiate
            setTimeout(function() {
                socket.emit('login', dataRet);
            },1000);

could change to something like:

          } else {
                var p = rows[0];
                var dataRet = new Player(p.id, p.nick, p.map_id, p.x, p.y, connexion, function () {
                    socket.emit('login', dataRet);
                }).toJson();
                console.log(dataRet);
            }

but here, as I said, nothing is asynchronous and also your dataRet is already set even before you run the setTimeout so this is not solving your problem, but it is answering your question.

rsp
  • 107,747
  • 29
  • 201
  • 177
  • yes my class.player.js exports "User" but in game.js I did "var Player = require('...') so whatever the name of the class I export, it's usable through "Player" in my game.js script (and it is copy-pasted exactly as-is). Thank you for the mysql escaping tip, I will correct my code. Also I have edited my original post. Thank you for your help – Mouradif Dec 17 '14 at 22:27