Sunday, March 24, 2019

nodejs require('net') cluster server emit connection to express app with http, https modules website chat bot

app.js



var cluster = require('cluster'), net = require('net');
var port = process.env.PORT || 8080;
var num_processes = process.env.WORK || require('os').cpus().length;
var fs = require('fs');
var options = {
pauseOnConnect: true,
key: fs.readFileSync(__dirname + '/private/server.key', 'utf8'),
cert: fs.readFileSync(__dirname + '/private/server.crt', 'utf8'),
requestCert: true,
rejectUnauthorized: false
}
if (cluster.isMaster) {
// This stores our workers. We need to keep them to be able to reference
// them based on source IP address. It's also useful for auto-restart, for example.
var workers = [];

// Helper function for spawning worker at index 'i'.
var spawn = function (i) {
workers[i] = cluster.fork();

// Optional: Restart worker on exit
workers[i].on('exit', function (code, signal) {
console.log('respawning worker', i);
spawn(i);
});
};

// Spawn workers.
for (var i = 0; i < num_processes; i++) {
spawn(i);
}

// Helper function for getting a worker index based on IP address.
// This is a hot path so it should be really fast. The way it works
// is by converting the IP address to a number by removing non numeric
// characters, then compressing it to the number of slots we have.
//
// Compared against "real" hashing (from the sticky-session code) and
// "real" IP number conversion, this function is on par in terms of
// worker index distribution only much faster.
var worker_index = function (ip, len) {
var s = '';
for (var i = 0, _len = ip.length; i < _len; i++) {
if (!isNaN(ip[i])) {
s += ip[i];
}
}
return Number(s) % len;
};

// Create the outside facing server listening on our port.
var server = net.createServer({ pauseOnConnect: true }, function (connection) {
// We received a connection and need to pass it to the appropriate
// worker. Get the worker for this connection's source IP and pass
// it the connection.
var worker = workers[worker_index(connection.remoteAddress, num_processes)];
worker.send('sticky-session:connection', connection);
}).listen(port);

console.log('Master server running on port: ' + port);
} else {
/*nodepro process running...*/
var core = require('./nodepro/core/start');
//if not use options with ssl then ERR_SSL_PROTOCOL_ERROR
var server = core.run(options);

// Listen to messages sent from the master. Ignore everything else.
process.on('message', function (message, connection) {
if (message !== 'sticky-session:connection') {
return;
}
//console.log(message, connection.remoteAddress);

// Emulate a connection event on the server by emitting the
// event with the connection the master sent us.
server.emit('connection', connection);

connection.resume();
});

console.log('ProcId : ' + process.pid + ' is listening to all incoming requests...');
}



nodepro/core/start.js



/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/


exports.run = function (credentials) {
// don't use yet in BOT
//var loader = require('./loader').instance();
// don't use yet
/*
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var redis = require('socket.io-redis');
var http = app.listen(80, function () {
console.log('ProcId : ' + process.pid + ' is listening to all incoming requests...');
});
*/

// Note we don't use a port here because the master listens on it for us.
var express = require('express'), path = require('path'),
http = require('http'), https = require('https');
//var app = express.createServer(credentials);
var app = new express();
// Don't expose our internal server to the outside.
var server = null;
if (credentials == undefined)
server = http.createServer(app);
else
server = https.createServer(credentials, app);
//var server = app.listen();

/*app use modules[web]*/
//var web_core = loader.module('web');
//web_core.init(express, app);
//or
app.use(express.static('public'));

/*server use modules[chat]*/
/*
if (loader.options.modules.chat.enable) {
var chat = loader.module('chat');
chat.init(server, app);
}
*/
/*server use modules[gamebot]*/
//var bot = loader.module('gamebot');
var bot = require('../modules/gamebot');
bot.init(server, app);

return server;
};



nodepro/modules/web.js


exports.init = function (express, app) {
var loader_package = require('../core/loader');
var loader = loader_package.instance();
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var sys_config = loader.config('sys.config');
var _opt = loader.options;

/*app.configuring*/
//set NODE_ENV=production&&node app.js
function debuggerModule(req, res, next) {
return next();
}
// debugging middleware in development only
if ('development' === app.get('env')) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
app.use(debuggerModule);
}
// process.env['CONSUMER_KEY'] = ""
// process.env['CONSUMER_SECRET'] = ""
// process.env['ACCESS_TOKEN_KEY'] = ""
// process.env['ACCESS_TOKEN_SECRET'] = ""

app.use(bodyParser.urlencoded({ extended: _opt.fw.urlencoded_extended }));// parse application/x-www-form-urlencoded
app.use(bodyParser.json());// parse application/json
app.set('json spaces', 1);
app.use(cookieParser());
app.use(_opt.res.static_path, express.static(loader.static_dir()));

if (_opt.fw.jade_engine) {
app.engine('jade', require('jade').__express);
app.set('view engine', 'jade');
}
if (_opt.fw.disable_view_cache) {
app.disable('view cache');
} else {
app.set('view cache', true);
}
if (_opt.fw.disable_etag) {
app.disable('etag');
}
if (_opt.fw.use_compression) {
var compression = require('compression');
app.use(compression());
}
if (_opt.fw.use_helmet) {
var helmet = require('helmet');
app.use(helmet());
app.use(helmet.noCache());
app.use(helmet.frameguard());
} else {
app.disable('x-powered-by');
}
if (_opt.fw.use_session) {
var sessionLib = loader.lib('session/' + _opt.fw.session_lib);
sessionLib.configure(express, app, loader);
}

/*app.routing*/
function init_loader_base(area_item, app_area) {
var new_loader = loader_package.instance();
var area_name = new_loader._comm.trim(app_area.mountpath, '/');
var base = {};
base.bag = {};
base.loader = new_loader;
base.area = area_name;
base.area_object = area_item;
base.app = app_area;
new_loader._base = base;
return new_loader._base;
}
function parallel(middlewares) {
var async = require("async");
return function (req, res, next) {
async.each(middlewares, function (mw, cb) {
mw(req, res, cb);
}, next);
};
}
function headers(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
}
function routing(req, res, next) {
console.log('routing..................');
headers(req, res, next);
var base = init_loader_base(undefined, app);
var router_package = base.loader.core('router');
var instance = router_package();
instance.route(base, req, res, next);
}
// Router middleware, mentioned it before defining routes.
/*areas*/
sys_config.areas.forEach(function (area_item) {
var app_area = new express();
app.use(area_item.uri, app_area);
app_area.use(function (req, res, next) {
headers(req, res, next);
var base = init_loader_base(area_item, app_area);
var router_package = base.loader.core('router');
var instance = router_package();
instance.route(base, req, res, next);
});
});
/*main*/
if (_opt.fw.use_parallel) {
app.use(parallel([routing]));
} else {
app.use(routing);
}
};



nodepro/modules/gamebot.js


exports.init = function (server, app) {
//var loader_package = require('../core/loader');
//var loader = loader_package.instance();
var socketio = require('socket.io');
//var socket_redis = require('socket.io-redis');
var io = socketio(server);
//var _opt = loader.options;
// Tell Socket.IO to use the redis adapter. By default, the redis
// server is assumed to be on localhost:6379. You don't have to
// specify them explicitly unless you want to change them.
//io.adapter(socket_redis({ host: 'localhost', port: 6379 }));
var botio = io.of('/bot'/*_opt.modules.gamebot.uri*/);

var uuidv4 = require('uuid/v4');
var rooms = {};
var sockets = {};
botio.on('connection', function (socket) {
//console.log('- io.sockets.server.engine.clientsCount = ', io.sockets.server.engine.clientsCount, ' -> ', sockets, ';');

socket.emit('detect', /* send to client */
{ uuid: uuidv4() }
); // emit an event to the socket
socket.on('context', function (data) { /* */
if (rooms[data.context] == undefined) {
rooms[data.context] = {};
}
rooms[data.context][data.player_id] = socket;
sockets[socket.id] = [data.context, data.player_id];
console.log('Connected:: ', data.context, ', ', data.player_id);
});
socket.on('message', function (data) { /* */
var room = rooms[data.context];
var players = Object.keys(room);
players.forEach(function (player_id) {
var friend_socket = room[player_id];
friend_socket.emit('message', data);
});
//var sockets_keys = Object.keys(sockets);
//console.log('sockets.length is ', sockets_keys.length);
//botio.emit('message', data);
// emit an event to all connected sockets
}); // listen to the event

socket.on('disconnect', function () {
var meta = sockets[socket.id];
console.log('(meta != undefined) is ', (meta != undefined));
if (meta != undefined) {
var context = meta[0];
var player_id = meta[1];
console.log('context.length is ', context, ', ', Object.keys(rooms[context]).length);
delete sockets[socket.id];
delete rooms[context][player_id];
console.log('context.length is ', context, ', ', Object.keys(rooms[context]).length);
}
var sockets_keys = Object.keys(sockets);
console.log('sockets.length is ', sockets_keys.length);
console.log('Disconnected:: ', socket.id);
});
});
};



public/index.html
.

<!DOCTYPE html>
<html>

<head>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
</head>

<body>
<script>
$(function () {
function getOrContextId() {
return 'room1';
}
$('#txt').val(window.location);
var my_id = undefined;
var socket = io(window.location + 'bot');
socket.on('detect', function (data) {
my_id = "ID:" + data.uuid;
socket.emit('context', { context: getOrContextId(), player_id: my_id });
$('ul').append($('<li>').html('Me-' + my_id + ': i connected'));
});
socket.on('message', function (data) {
if (data.text !== undefined) {
if (my_id !== data.player_id)
$('ul').append($('<li>').html('Friend-' + data.player_id + ': ' + data.text));
else
$('ul').append($('<li>').html('Me-' + my_id + ': ' + data.text));
}
});
$('#btn').click(function () {
socket.emit('message', {
context: getOrContextId(),
player_id: my_id,
text: $('#txt').val()
});
});
});
</script>
<input type="text" id="txt">
<a id="btn" href="javascript:void(0);">Send</a>
<ul>
<li></li>
</ul>
</body>

</html>


Sunday, March 10, 2019

facebook instant game custom server communication Nodejs, PHP

game.js

const BACKEND_URL = "https://localhost/quizbattle";
const BACKEND_URL_API = BACKEND_URL + "/api";
var game = {};
var gameData = {};
var gameOptions = {
    width: 0,
    height: 0,
    backgroundColor: 0xFFFFFF,
    localStorageName: "__ab__"
};
var gameStuff = {
    playerId: "",
    playerName: "",
    playerPic: "",
    locale: ""
};

window.onload = function () {
    $('#home-section').hide();

    FBInstant.initializeAsync().then(function () {
        FBInstant.startGameAsync().then(function () {
            gameStuff.playerId = FBInstant.player.getID();
            gameStuff.playerName = FBInstant.player.getName();
            gameStuff.playerPic = FBInstant.player.getPhoto();
            gameStuff.locale = FBInstant.getLocale();
            gameStuff.contextType = FBInstant.context.getType();

            gameOptions.height = window.innerHeight;
            gameOptions.width = window.innerWidth / 2;

            $('#u-photo-field').attr('src', gameStuff.playerPic);
            $('#u-name-field').html(gameStuff.playerName);
            $('#context-type').html(gameStuff.contextType);

            $('#home-section').show();
           
            loadingInitialData();
        });
    });
};

function getOrCreateContextId() {
    var contextType = FBInstant.context.getType();
    var contextId = FBInstant.context.getID();
   
    console.log(contextType);
    if (contextType == 'SOLO') {
        contextId = FBInstant.player.getID() + '_SOLO';
    }
    return contextId;
}

function showMessage(div, message) {
    document.getElementById(div).innerHTML = message;
}

function loadingInitialData() {
    var contextId = getOrCreateContextId();

    FBInstant.player.getSignedPlayerInfoAsync(contextId)
            .then(function (signedPlayerInfo) {
                // Requesting data from backend passing the signature as an argument
                // The backend APP can retrieve contextId from the signature
                var signature = signedPlayerInfo.getSignature();
                return new backendClient(BACKEND_URL_API).load(contextId, signature);
            })
            .then(function (response) {
                // Got successful response from backend
                console.log('Loaded from backend', response);
                if (response.empty) {
                    showMessage('test', 'No data');
                } else {
                    showMessage('test', JSON.stringify(response.data));
                }
            }.bind(this))
            .catch(function (error) {
                // Got error response from backend
                console.error('Not loaded from backend', error);
                showMessage('error-messages', 'Error loading backend data:' + error.message);
            }.bind(this));
}

function singlePlayBoot() {

}

function multiPlayBoot() {

}

function leaderBoardBoot() {

}

function helpBoot() {

}


node.js server side


var pg = require('pg');
var crypto = require('crypto-js');

module.exports = function(app) {

app.post('/get-match', function(request, response) {
        var signature = request.body.signature;
       
        var isValid = validate(signature);
       
        if (isValid) {
            var contextId = getEncodedData(signature);
            loadMatchDataAsync(contextId)
            .then(function(result){
                if (result) {
                    response.json({'success':true, 'contextId':contextId, 'empty': false, 'data':result});
                } else {
                    response.json({'success':true, 'contextId':contextId, 'empty': true});
                }
            })
            .catch(function(err){
                response.json({'success':false, 'error':err});
            });
        } else {
            console.log('encoded data', getEncodedData(signature));
            response.json({'success':false, 'error':'invalid signature'});
        }
       
    })

loadMatchDataAsync = function(contextId) {
        return new Promise(function(resolve, reject){
            pg.connect(process.env.DATABASE_URL, function(err, client, done) {
                client.query('SELECT * FROM matches WHERE context = $1::text', [contextId], function(err, result) {
                    done();
                    if (err) {
                        reject(err);
                    }
                    if (result.rows.length > 0) {
                        resolve(result.rows[0].data);
                    } else {
                        resolve();
                    }
                });
            });
        });
    };
   
    validate = function(signedRequest) {
        try{
           
            var firstpart = signedRequest.split('.')[0];
            var replaced = firstpart.replace(/-/g, '+').replace(/_/g, '/');
            var signature = crypto.enc.Base64.parse(replaced).toString();
            const dataHash = crypto.HmacSHA256(signedRequest.split('.')[1], process.env.APP_SECRET).toString();
            var isValid = signature === dataHash;
            if (!isValid) {
                console.log('Invalid signature');
                console.log('firstpart', firstpart);
                console.log('replaced ', replaced);
                console.log('Expected', dataHash);
                console.log('Actual', signature);
            }
           
            return isValid;
        } catch (e) {
            return false;
        }
    };
   
    getEncodedData = function(signedRequest) {
        try {
           
            const json = crypto.enc.Base64.parse(signedRequest.split('.')[1]).toString(crypto.enc.Utf8);
            const encodedData = JSON.parse(json);
           
            /*
            Here's an example of encodedData can look like
            {
                algorithm: 'HMAC-SHA256',
                issued_at: 1520009634,
                player_id: '123456789',
                request_payload: 'backend_save'
            }
            */
           
            return encodedData.request_payload;
        } catch (e) {
            return null;
        }
    };
}


PHP server side


private function _validate($signedRequest) {
        try {
            list($encoded_signature, $payload) = explode('.', $signedRequest, 2);

            // decode the data
            $signature = base64_url_decode($encoded_signature);
            //$data = json_decode(base64_url_decode($payload), true);
           
            // confirm the signature
            $expected_signature = hash_hmac('sha256', $payload, $this->facebook_config['app_secret'], $raw = true);
            $isValid = $signature === $expected_signature;

            if (!$isValid) {
                error_log('Bad Signed JSON signature!');
                return null;
            }

            return $isValid;
        } catch (Exception $ex) {
            return false;
        }
    }

    private function _getEncodedData($signedRequest) {
        try {
            $encodedData = json_decode(base64_url_decode(explode('.', $signedRequest)[1]), true);
            /*
            $encodedData = {
                "status":true,
                "data":{
                    "algorithm":"HMAC-SHA256",
                    "issued_at":1552157956,
                    "player_id":"2522121991191915",
                    "request_payload":"2522121991191915_SOLO"
                }
            }
             */

            return $encodedData;
        } catch (Exception $ex) {
            return null;
        }
    }


function load_post() {

$signature = $this->post('signature');

            $isValid = $this->_validate($signature);

            if ($isValid) {
                $edata = $this->_getEncodedData($signature);
                $this->success($edata->data);
            } else {
                $this->error('Invalid Request!');
            }
..........................
}

Tuesday, March 5, 2019

how-to-integrate-facebook-instant-games tutorial Phaser

1.

https://www.emanueleferonato.com/2018/03/28/how-to-integrate-facebook-instant-games-api-in-your-phaser-game/


2.

https://www.emanueleferonato.com/2018/04/05/how-to-integrate-facebook-instant-games-api-in-your-phaser-game-displaying-player-name-and-profile-picture/


3.
https://www.facebook.com/embed/instantgames/<app_id>/player?game_url=https://localhost/mygame