cloudscript-server
Version:
A local environment for cloudscript development
189 lines (175 loc) • 6.58 kB
JavaScript
require('colors');
const express = require('express');
const axios = require('axios').default;
const compiler = require('./compiler.js');
const directory = process.argv[3];
require('dotenv').config({ path: require('path').join(directory, './.env') });
if (process.env['TITLE_ID'] == null) {
console.log('missing environment variable TITLE_ID'.red);
process.exit();
}
if (process.env['TITLE_SECRET'] == null) {
console.log('missing environment variable TITLE_SECRET'.red);
process.exit();
}
const titleId = process.env['TITLE_ID'];
let serverEntityTokenExpiration = null;
let cloudscript = null;
try {
cloudscript = require('./cloudscript.js');
}
catch (e) {
compiler.transformErrorStack(e, directory);
logError(e);
process.exit();
}
async function executeCloudScript(req, res) {
let startTime = Date.now();
try {
IS_DEV = true;
currentPlayerId = req.body.PlayFabId ?? extractPlayfabidFromToken(req.headers['x-authorization']);//doing this is faster than validating the ticket with the playfab api :P, it can fail obviously
__playfab_internal.apiCallCount = 0;
__playfab_internal.httpRequestCount = 0;
__playfab_internal.logs = [];
if (cloudscript[req.body.FunctionName] == null)
return res.json(generateResponse(200, 'OK', req.body.FunctionName, null, (Date.now() - startTime) * 0.001, {
Error: "CloudScriptNotFound", Message: `No function named ${req.body.FunctionName} was found to execute`, StackTrace: ""
}));
let result = cloudscript[req.body.FunctionName](req.body.FunctionParameter, { playerProfile: null, playStreamEvent: null, triggeredByTask: null });
return res.json(generateResponse(200, 'OK', req.body.FunctionName, result, (Date.now() - startTime) * 0.001));
}
catch (e) {
compiler.transformErrorStack(e, directory);
logError(e);
if (e.data?.code != null) {
return res.status(e.data.code).json(e.data);
}
if (e.stack != null) {
return res.json(generateResponse(200, 'OK', req.body.FunctionName, null, (Date.now() - startTime) * 0.001, {
Error: "JavascriptException", Message: "JavascriptException", StackTrace: e.stack
}));
}
return res.status(500).json({ error: 'Unknown', code: 500 });
}
}
function generateResponse(code, status, FunctionName, FunctionResult, ExecutionTimeSeconds, Error) {
return {
code,
status,
data: {
FunctionName,
Revision: 0,
FunctionResult,
APIRequestsIssued: __playfab_internal.apiCallCount,
HttpRequestsIssued: __playfab_internal.httpRequestCount,
ExecutionTimeSeconds,
Logs: __playfab_internal.logs,
Error
}
};
}
function extractPlayfabidFromToken(token) {
let parts = token.split('-');
if (parts.length > 1)
return parts[0];
let fromBase64 = Buffer.from(token, 'base64').toString('ascii');
parts = fromBase64.split('|');
var payload = JSON.parse(parts[2]);
return payload.ec.split('/')[2];
}
const app = express();
app.use(express.json());
app.post('/Client/ExecuteCloudScript', executeCloudScript);
app.post('/Server/ExecuteCloudScript', executeCloudScript);
//proxy every other request to playfab api
app.use('*', async (req, res) => {
try {
let route = req.params[0];
let url = `https://${titleId}.playfabapi.com${route}`;
let headers = {};
for (let key in req.headers) {
if (key == 'host')
continue;
if (key == 'connection')
continue;
if (key == 'content-length')
continue;
if (key.includes('encoding'))
continue;
headers[key] = req.headers[key];
}
let response = await axios({
headers: headers,
url: url,
method: req.method,
data: req.body
});
let responseHeaders = response.headers;
for (let key in responseHeaders) {
res.setHeader(key, responseHeaders[key]);
}
res.status(response.status).json(response.data);
}
catch (e) {
console.error(e);
res.status(e.response.status).json(e.response.data);
}
});
async function setupServerEntityToken() {
if (serverEntityTokenExpiration != null && Date.now() - serverEntityTokenExpiration > 60 * 60 * 1000) {
setTimeout(setupServerEntityToken, 300000);
return;
}
let playfab = require('playfab-sdk');
let res = await require('util').promisify(playfab.PlayFabAuthentication.GetEntityToken)({});
playfab.settings.entityToken = res.data.EntityToken;
serverEntityTokenExpiration = Date.parse(res.data.TokenExpiration);
setTimeout(setupServerEntityToken, 300000);
}
async function startServer() {
let playfab = require('playfab-sdk');
playfab.settings.titleId = process.env['TITLE_ID'];
playfab.settings.developerSecretKey = process.env['TITLE_SECRET'];
await setupServerEntityToken();
let port = parseInt(process.argv[2]);
app.listen(port);
console.log(("Server started at port: " + port + "\n").green);
}
startServer();
//used by the global playfab log object
global.__convertAndLogTrace = function (data) {
try {
let dummy = new Error("dummy");//doing this to get the stack
compiler.transformErrorStack(dummy, directory);
let stackLines = dummy.stack.split('\n');
// stackLines.splice(0, 4);
data.Stack = stackLines.join('\n');
console.log(JSON.stringify(data).yellow);
}
catch (e) {
}
}
//custom colored error
function logError(e) {
if (e.stack == null) {
console.error(e);
}
else {
if (typeof e.data == 'object')
console.error(JSON.stringify(e.data).red);
console.log(e.stack.red);
}
}
//listening if monitor is still controlling the process, if not, exit
function listenMonitor() {
let exitTimeout = null;
function exitProgram() {
process.exit();
}
process.stdin.on('data', (data) => {
if (exitTimeout != null)
clearTimeout(exitTimeout);
exitTimeout = setTimeout(exitProgram, 15000);
});
}
listenMonitor();