- Added api support
- Improved first run setup detection
- Better db instance access
- Cleaner routing
This commit is contained in:
Felix 2016-08-27 13:20:28 +02:00
parent 59c2bc0767
commit c66efe50b4
13 changed files with 221 additions and 101 deletions

1
.save Normal file
View File

@ -0,0 +1 @@
{"isFirstRun":false,"allowedApiHosts":["localhost"],"loginDetails":{"username":"root","password":"13377"}}

View File

@ -6,8 +6,8 @@ export default {
// show greeting
GREET: true,
// emit api calls
ENABLE_API: false,
// allow api calls
ENABLE_API: true,
// Server settings
MAX_CONNECTIONS: 64,

View File

@ -21,6 +21,8 @@
"babel-cli": "^6.11.4",
"babel-preset-es2015": "^6.13.1",
"babel-preset-stage-0": "^6.5.0",
"url": "^0.11.0",
"prompt": "^1.0.0",
"fs-extra": "^0.30.0",
"mysql": "^2.11.1",
"jwt-decode": "^2.1.0",

View File

@ -1,44 +1,138 @@
import fs from "fs";
import url from "url";
import prompt from "prompt";
import CFG from "../cfg";
export function ready(e) {
if (e) console.log(e);
else console.log("Ready!");
prompt.start({
message: " ",
delimiter: " "
});
export function processApiCall(req, res, route) {
let save = JSON.parse(fs.readFileSync(".save", "utf8"));
let allowedHosts = save.allowedApiHosts;
let hoster = url.parse(req.headers.referer).host;
if (!(allowedHosts.indexOf(hoster) > -1)) {
this.grantApiAccess(req, res, route);
return void 0;
}
let raw = req.body.toString();
let json = null;
try {
json = JSON.parse(raw);
} catch (e) {
this.print(e, 31);
this.answerApiCall(res, "");
return void 0;
}
if (this.isApiCall(json)) {
json.host = hoster;
if (json.action === "login") {
let result = this["api_login"](json);
this.answerApiCall(res, JSON.stringify(result));
}
else {
if (this.apiClients[hoster]) {
let result = this["api_" + json.action](json);
this.answerApiCall(res, JSON.stringify(result));
}
else {
this.print(`${hoster} isnt logged in! Kicking..`, 31);
}
}
}
else {
if (json.action === "init") {
this.answerApiCall(res, JSON.stringify({ success: true }));
}
}
}
/**
* @param {String} msg
* @param {Number} color
* @param {Boolean} nl
*/
export function print(msg, color, newline) {
color = Number.isInteger(color) ? color : CFG.DEFAULT_CONSOLE_COLOR;
process.stdout.write(`[Console] \x1b[${color};1m${msg}\x1b[0m${newline === void 0 ? "\n" : ""}`);
export function grantApiAccess(req, res, route) {
let save = JSON.parse(fs.readFileSync(".save", "utf8"));
let hoster = url.parse(req.headers.referer).host;
let msg = `[Console] \x1b[33mGrant API access to ${hoster}?\x1b[0m`;
prompt.get([{ name: "grant", required: true, description: msg }], (e, result) => {
if (result.grant === "y" || result.grant === "yes") {
save.allowedApiHosts.push(hoster);
fs.writeFileSync(".save", JSON.stringify(save), "utf8");
this.print(`Successfully added ${hoster} to allowed API hosts!`);
this.processApiCall(req, res, route);
}
else {
this.print(`Denied API access for ${hoster}`, 31);
this.answerApiCall(res, "");
}
});
}
/**
* @param {Player} player
*/
export function registerPlayer(player) {
console.log(player.username + " registered!");
export function answerApiCall(res, data) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
res.setHeader("Access-Control-Allow-Credentials", true);
res.end(data);
}
/**
* @param {Player} player
*/
export function loginPlayer(player) {
console.log(player.username + " logged in!");
export function api_login(data) {
if (typeof data !== "object") return void 0;
let save = JSON.parse(fs.readFileSync(".save", "utf8"));
let success = false;
let username = save.loginDetails.username;
let password = save.loginDetails.password;
if (
username === data.username &&
password === data.password
) {
success = true;
if (!this.apiClients[data.host]) {
this.print(`API access for ${data.host} granted!`);
}
this.print(`${data.host} logged in!`, 36);
this.apiClients[data.host] = {
timestamp: +new Date()
};
}
return ({
success: success
});
}
/**
* @param {Player} player
*/
export function killPlayer(player) {
console.log(player.username + " killed!");
export function api_getConnectedPlayers() {
return ({
connected_players: this.world.players.length
});
}
/**
* @param {Player} player
*/
export function updatePlayerAvatar(player) {
console.log("Updated avatar of " + player.username + "!");
export function api_getServerVersion() {
return ({
version: CFG.VERSION
});
}
export function api_spawnPkmnToPlayer(data) {
let name = String(data.player);
let pkmn = String(data.pkmn).toUpperCase();
this.print(`Spawned 1x ${pkmn}'s to ${name}!`);
return ({
success: true
});
}

View File

@ -3,7 +3,7 @@ import CFG from "../../cfg";
export function createTableIfNotExists(name) {
return new Promise((resolve) => {
this.db.instance.query(`SHOW TABLES LIKE '${name}';`, (e, rows, fields) => {
this.db.query(`SHOW TABLES LIKE '${name}';`, (e, rows, fields) => {
if (e) console.log(e);
else {
// exists
@ -39,7 +39,7 @@ export function createTable(name) {
`;
return new Promise((resolve) => {
this.db.instance.query(query, (e, rows) => {
this.db.query(query, (e, rows) => {
if (e) console.log(e);
else resolve();
});

View File

@ -7,7 +7,7 @@ import CFG from "../../cfg";
*/
export function getQueryByColumnFromTable(column, value, table) {
return new Promise((resolve) => {
this.db.instance.query(`SELECT * FROM ${table} WHERE ${column}=?`, [value], (e, rows) => {
this.db.query(`SELECT * FROM ${table} WHERE ${column}=?`, [value], (e, rows) => {
if (e) console.log(e);
if (rows && rows.length) resolve(rows);
else resolve(void 0);
@ -22,7 +22,7 @@ export function getQueryByColumnFromTable(column, value, table) {
*/
export function deleteQueryByColumnFromTable(column, value, table) {
return new Promise((resolve) => {
this.db.instance.query(`DELETE FROM ${table} WHERE ${column}=?`, [value], (e, rows) => {
this.db.query(`DELETE FROM ${table} WHERE ${column}=?`, [value], (e, rows) => {
if (e) console.log(e);
else resolve(void 0);
});

View File

@ -19,7 +19,7 @@ export function setupDatabaseConnection() {
this.retry("Retrying again in ", () => this.setupDatabaseConnection().then(resolve), 5);
return void 0;
}
this.db.instance = connection;
this.db = connection;
this.createTableIfNotExists(CFG.MYSQL_USERS_TABLE).then(() => {
this.createTableIfNotExists(CFG.MYSQL_OWNED_PKMN_TABLE).then(() => {
this.print(`\x1b[36;1mMySQL\x1b[0m\x1b[32;1m connection established\x1b[0m`);
@ -39,7 +39,7 @@ export function setupDatabaseConnection() {
* @param {Function} resolve
*/
export function closeConnection(resolve) {
this.db.instance.end(() => {
this.db.end(() => {
resolve();
});
}

View File

@ -18,9 +18,7 @@ export function createHTTPServer() {
req.on("end", () => {
let buffer = Buffer.concat(chunks);
req.body = buffer;
this.world.getPlayerByRequest(req, res).then((player) => {
this.routeRequest(player, req, res);
});
this.routeRequest(req, res);
});
});
server.listen(CFG.PORT);

View File

@ -1,6 +1,5 @@
import fs from "fs";
import os from "os";
import EventEmitter from "events";
import POGOProtos from "pokemongo-protobuf";
import {
@ -30,23 +29,20 @@ const greetMessage = fs.readFileSync(".greet", "utf8");
/**
* @class GameServer
*/
export default class GameServer extends EventEmitter {
export default class GameServer {
/** @constructor */
constructor() {
super(null);
this.STATES = {
PAUSE: false,
DEBUG: false,
CRASH: false
};
this.db = {
instance: null,
collections: {}
};
this.db = null;
this.apiClients = {};
this.assets = {};
this.master = null;
@ -61,8 +57,6 @@ export default class GameServer extends EventEmitter {
this.timeoutTick = 0;
this.passedTicks = 0;
this.initAPI();
if (CFG.GREET) this.greet();
this.print(`Booting Server v${require("../package.json").version}-dev`, 33);
@ -73,25 +67,25 @@ export default class GameServer extends EventEmitter {
}
initAPI() {
if (CFG.ENABLE_API) {
for (let key in _api) {
this.on(key, _api[key]);
};
}
// make sure we still have our print fn
else {
this.on("print", _api["print"]);
}
}
/**
* @param {String} msg
* @param {Number} color
* @param {Boolean} nl
*/
print(msg, color, nl) {
this.emit("print", msg, color, nl);
print(msg, color, newline) {
color = Number.isInteger(color) ? color : CFG.DEFAULT_CONSOLE_COLOR;
process.stdout.write(`[Console] \x1b[${color};1m${msg}\x1b[0m${newline === void 0 ? "\n" : ""}`);
}
/**
* @param {Object} obj
* @return {Boolean}
*/
isApiCall(call) {
let action = String(call.action);
return (
"api_" + action in _api
);
}
/**
@ -163,6 +157,7 @@ export default class GameServer extends EventEmitter {
}
inherit(GameServer, _api);
inherit(GameServer, _dump);
inherit(GameServer, _http);
inherit(GameServer, _setup);

View File

@ -20,14 +20,14 @@ export default class Player extends MapObject {
this.response = obj.response;
this.authenticated = 0;
/*
this.bag = new Bag(this);
this.info = new Info(this);
this.pary = new Party(this);
this.avatar = new Avatar(this);
this.pokedex = new Pokedex(this);
this.tutorial = new Tutorial(this);
*/
}
}

View File

@ -8,7 +8,11 @@ import CFG from "../../../cfg";
export default class World {
/** @constructor */
constructor() {
constructor(instance) {
this.instance = instance;
this.db = this.instance.db;
this.players = [];
@ -31,16 +35,14 @@ export default class World {
let player = null;
return new Promise((resolve) => {
if (this.playerAlreadyConnected(req)) {
player = this.getPlayerByIP(req.headers.host);
resolve(player);
}
else {
this.addPlayer(req, res);
resolve(player);
}
});
if (this.playerAlreadyConnected(req)) {
player = this.getPlayerByIP(req.headers.host);
return (player);
}
else {
this.addPlayer(req, res);
return (player);
}
}

View File

@ -6,11 +6,10 @@ import POGOProtos from "pokemongo-protobuf";
import CFG from "../cfg";
/**
* @param {Player} player
* @param {Request} req
* @param {Response} res
*/
export function routeRequest(player, req, res) {
export function routeRequest(req, res) {
let parsed = url.parse(req.url).pathname;
let route = parsed.split("/");
@ -19,31 +18,60 @@ export function routeRequest(player, req, res) {
switch (route[1]) {
case "plfe":
case "custom":
if (route[2] === "rpc") this.onRequest(player, req);
this.processRpcRequest(req, res, route);
break;
case "model":
// make sure no random dudes can access download
if (!player.authenticated) return void 0;
let name = route[2];
if (name && name.length > 1) {
let folder = player.isAndroid ? "android/" : "ios/";
fs.readFile("data/" + folder + name, (error, data) => {
if (error) {
this.print(`Error file resolving model ${name}:` + error, 31);
return void 0;
}
this.print(`Sent ${name} to ${player.email}`, 36);
res.end(data);
});
this.processModelRequest(req, res, route);
break;
case "api":
if (!CFG.ENABLE_API) return void 0;
if (req.method === "POST") {
this.processApiCall(req, res, route);
}
break;
default:
console.log(`Unknown request url: https://${req.headers.host}${req.url}`);
console.log(`Unknown request url: https://${host}${req.url}`);
break;
};
}
/**
* @param {Request} req
* @param {Response} res
* @param {Array} route
*/
export function processRpcRequest(req, res, route) {
let player = this.world.getPlayerByRequest(req, res);
if (route[2] === "rpc") this.onRequest(player, req);
}
/**
* @param {Request} req
* @param {Response} res
* @param {Array} route
*/
export function processModelRequest(req, res, route) {
let player = this.world.getPlayerByRequest(req, res);
// make sure no random dudes can access download
if (!player.authenticated) return void 0;
let name = route[2];
if (name && name.length > 1) {
let folder = player.isAndroid ? "android/" : "ios/";
fs.readFile("data/" + folder + name, (error, data) => {
if (error) {
this.print(`Error file resolving model ${name}:` + error, 31);
return void 0;
}
this.print(`Sent ${name} to ${player.email}`, 36);
res.end(data);
});
}
}
/**
* @param {Player} player
* @param {Request} req

View File

@ -16,12 +16,14 @@ import {
export function setup() {
let isFirstRun = !this.fileExists(CFG.DUMP_ASSET_PATH);
let save = JSON.parse(fs.readFileSync(".save", "utf8"));
if (isFirstRun) {
this.print("Required assets are missing! Preparing dump session..", 31);
if (save.isFirstRun) {
this.print("Required assets are missing! Preparing dump session..", 33);
setTimeout(() => {
this.onFirstRun(() => {
save.isFirstRun = false;
fs.writeFileSync(".save", JSON.stringify(save), "utf8");
this.setup();
});
}, 1e3);
@ -46,13 +48,11 @@ export function setup() {
setTimeout(this::this.cycle, 1);
let localIPv4 = this.getLocalIPv4();
this.print(`Server listening at ${localIPv4}:${CFG.PORT}`, 33);
this.emit("ready", void 0);
});
}).catch((e) => {
//fse.removeSync(CFG.DUMP_ASSET_PATH);
this.print("Error: " + e + " was not found!", 31);
this.emit("ready", e);
});
}