braincloud
Version:
brainCloud client for NodeJS
1,593 lines (1,427 loc) • 686 kB
JavaScript
//> ADD IF K6
//+ import crypto from 'k6/crypto';
//+ import http from 'k6/http';
//+ var responseG = {};
//+ var CryptoJS = crypto;
//+ var window = {
//+ XMLHttpRequest: http
//+ }
//+ export function getRes(){
//+ return responseG;
//+ }
//+ var localStorage = {
//+ lastPacketId:"",
//+ anonymousId:"",
//+ profileId:"",
//+ sessionId:"",
//+ setItem:function(key, value) {
//+ this[key] = value;
//+ },
//+ getItem:function(item) {
//+ return this[item];
//+ }
//+ };
//> END
//> REMOVE IF K6
if (typeof CryptoJS === 'undefined' || CryptoJS === null) {
var CryptoJS = require('crypto-js');
}
//> END
function BrainCloudManager ()
{
var bcm = this;
//> REMOVE IF K6
var _setInterval = typeof customSetInterval === 'function' ? customSetInterval : setInterval;
//> END
bcm.name = "BrainCloudManager";
bcm._sendQueue = [];
bcm._inProgressQueue = [];
bcm._abTestingId = -1;
bcm._sessionId = "";
bcm._packetId = 0;
bcm._loader = null;
bcm._eventCallback = null;
bcm._rewardCallback = null;
bcm._errorCallback = null;
bcm._jsonedQueue = "";
bcm._idleTimeout = 30;
bcm._heartBeatIntervalId = null;
bcm._bundlerIntervalId = null;
bcm._packetTimeouts = [15, 20, 35, 50];
bcm._retry = 0;
bcm._requestId = 0; // This is not like packet id. We need this to make sure we don't trigger events on XMLHttpRequest responses if we already moved on. Had issues with retrying and losing internet
bcm._appId = "";
bcm._secret = "";
bcm._secretMap = {};
bcm._serverUrl = "https://api.braincloudservers.com";
bcm._dispatcherUrl = bcm._serverUrl + "/dispatcherv2";
bcm._fileUploadUrl = bcm._serverUrl + "/uploader";
bcm._appVersion = "";
bcm._debugEnabled = false;
bcm._compressionEnabled = true;
bcm._requestInProgress = false;
bcm._bundleDelayActive = false;
bcm._statusCodeCache = 403;
bcm._reasonCodeCache = 40304;
bcm._statusMessageCache = "No session";
//kill switch
bcm._killSwitchThreshold = 11;
bcm._killSwitchEngaged = false;
bcm._killSwitchErrorCount = 0;
bcm._killSwitchService = "";
bcm._killSwitchOperation = "";
bcm._isInitialized = false;
bcm._isAuthenticated = false;
bcm.compressRequest = function(requestToCompress) {
var encodedData = new TextEncoder().encode(requestToCompress);
var compressionStream = new Blob([encodedData]).stream().pipeThrough(new CompressionStream("gzip"));
return new Response(compressionStream).blob()
.then(function(compressedBlob) {
return compressedBlob.arrayBuffer();
})
.catch(function(error) {
console.error("Error during compression:", error);
throw error;
});
};
bcm.initialize = function(appId, secret, appVersion)
{
bcm._appId = appId;
bcm._secret = secret;
bcm._secretMap = {};
bcm._secretMap[appId] = secret;
bcm._appVersion = appVersion;
bcm._isInitialized = true;
};
bcm.initializeWithApps = function(defaultAppId, secretMap, appVersion)
{
bcm._appId = defaultAppId;
bcm._secret = secretMap[defaultAppId];
bcm._secretMap = secretMap;
bcm._appVersion = appVersion;
bcm._isInitialized = true;
};
bcm.setServerUrl = function(serverUrl)
{
bcm._serverUrl = serverUrl;
if (bcm._serverUrl.endsWith("/dispatcherv2"))
{
bcm._serverUrl = bcm._serverUrl.substring(0, bcm._serverUrl.length - "/dispatcherv2".length);
}
while (bcm._serverUrl.length > 0 && bcm._serverUrl.charAt(bcm._serverUrl.length - 1) == '/')
{
bcm._serverUrl = bcm._serverUrl.substring(0, bcm._serverUrl.length - 1);
}
bcm._dispatcherUrl = bcm._serverUrl + "/dispatcherv2";
bcm._fileUploadUrl = bcm._serverUrl + "/uploader";
};
bcm.getDispatcherUrl = function()
{
return bcm._dispatcherUrl;
};
bcm.getFileUploadUrl = function()
{
return bcm._fileUploadUrl;
};
bcm.setABTestingId = function(abTestingId)
{
bcm._abTestingId = abTestingId;
};
bcm.getABTestingId = function()
{
return bcm._abTestingId;
};
bcm.getSessionId = function()
{
return bcm._sessionId;
};
bcm.setSessionId = function(sessionId)
{
if(sessionId !== null || sessionId !== "")
{
bcm._isAuthenticated = true;
}
else
{
bcm._packetId = 0;
}
bcm._sessionId = sessionId;
};
bcm.getSecret = function()
{
return bcm._secret;
};
bcm.setSecret = function(secret)
{
bcm._secret = secret;
};
bcm.getAppVersion = function()
{
return bcm._appVersion;
};
bcm.setAppVersion = function(appVersion)
{
bcm._appVersion = appVersion;
};
bcm.getAppId = function()
{
return bcm._appId;
};
bcm.setAppId = function(appId)
{
bcm._appId = appId;
};
bcm.registerEventCallback = function(eventCallback)
{
bcm._eventCallback = eventCallback;
};
bcm.deregisterEventCallback = function()
{
bcm._eventCallback = null;
};
bcm.registerRewardCallback = function(rewardCallback)
{
bcm._rewardCallback = rewardCallback;
};
bcm.deregisterRewardCallback = function()
{
bcm._rewardCallback = null;
};
bcm.setErrorCallback = function(errorCallback)
{
bcm._errorCallback = errorCallback;
};
bcm.setDebugEnabled = function(debugEnabled)
{
bcm._debugEnabled = debugEnabled;
};
bcm.isInitialized = function()
{
return bcm._isInitialized;
};
bcm.isAuthenticated = function()
{
return bcm._isAuthenticated;
};
bcm.setAuthenticated = function()
{
bcm._isAuthenticated = true;
bcm.startHeartBeat();
};
bcm.debugLog = function(msg, isError)
{
if(bcm._debugEnabled === true) {
if (isError)
{
console.error (msg);
}
else
{
console.log (msg);
}
}
}
bcm.sendRequest = function(request)
{
bcm.debugLog("SendRequest: " + JSON.stringify(request));
//> ADD IF K6
//+ // todo : temporary way of adding this for k6 test
//+ bcm._requestInProgress = false;
//> END
bcm._sendQueue.push(request);
if (!bcm._requestInProgress && !bcm._bundleDelayActive)
{
//> ADD IF K6
//+ bcm._bundleDelayActive = false;
//+ bcm.processQueue();
//> END
//> REMOVE IF K6
// We can exploit the fact that JS is single threaded and process
// the queue 1 "frame" later. This way if the user is doing many
// consecussive calls they will be bundled
bcm._bundleDelayActive = true;
setTimeout(function()
{
bcm._bundleDelayActive = false;
bcm.processQueue();
}, 0);
//> END
}
};
bcm.resetCommunication = function()
{
bcm.stopHeartBeat();
bcm._sendQueue = [];
bcm._inProgressQueue = [];
bcm._sessionId = "";
bcm.packetId = 0;
bcm._isAuthenticated = false;
bcm._requestInProgress = false;
bcm.resetErrorCache();
};
bcm.resetErrorCache = function()
{
bcm._statusCodeCache = 403;
bcm._reasonCodeCache = 40304;
bcm._statusMessageCache = "No session";
}
bcm.updateKillSwitch = function(service, operation, statusCode)
{
if (statusCode === bcm.statusCodes.CLIENT_NETWORK_ERROR)
{
return;
}
if (bcm._killSwitchService.length === 0)
{
bcm._killSwitchService = service;
bcm._killSwitchOperation = operation;
bcm._killSwitchErrorCount++;
}
else if (service === bcm._killSwitchService && operation === bcm._killSwitchOperation)
{
bcm._killSwitchErrorCount++;
}
if (!bcm._killSwitchEngaged && bcm._killSwitchErrorCount >= bcm._killSwitchThreshold)
{
bcm._killSwitchEngaged = true;
bcm.debugLog("Client disabled due to repeated errors from a single API call: " + service + " | " + operation);
}
}
bcm.resetKillSwitch = function()
{
bcm._killSwitchErrorCount = 0;
bcm._killSwitchService = "";
bcm._killSwitchOperation = "";
}
bcm.startHeartBeat = function()
{
bcm.stopHeartBeat();
//> REMOVE IF K6
bcm._heartBeatIntervalId = _setInterval(function()
{
bcm.sendRequest({
service : "heartbeat",
operation : "READ",
callback : function(result) {}
});
}, bcm._idleTimeout * 1000);
//> END
}
bcm.stopHeartBeat = function()
{
if (bcm._heartBeatIntervalId)
{
//> REMOVE IF K6
clearInterval(bcm._heartBeatIntervalId);
//> END
bcm._heartBeatIntervalId = null;
}
}
//Handle response bundles with HTTP 200 response
bcm.handleSuccessResponse = function(response)
{
var messages = response["responses"];
if (bcm._debugEnabled)
{
for (var c = 0; c < messages.length; ++c)
{
if (messages[c].status == 200)
{
bcm.debugLog("Response(" + messages[c].status + "): " +
JSON.stringify(messages[c]));
}
else
{
bcm.debugLog("Response(" + messages[c].status + "): " +
JSON.stringify(messages[c]), true);
}
}
}
for (var c = 0; c < bcm._inProgressQueue.length && c < messages.length; ++c)
{
var callback = bcm._inProgressQueue[c].callback;
if (bcm._inProgressQueue[c] != null && bcm._errorCallback && messages[c].status != 200)
{
bcm._errorCallback(messages[c]);
}
if (bcm._inProgressQueue[c] == null) return; //comms was reset
if (messages[c].status == 200)
{
bcm.resetKillSwitch();
var data = messages[c].data;
// A session id or a profile id could potentially come back in any messages
//only save cached session and profile id when its an authentication or identity service being used.
if (data && (bcm._inProgressQueue[c].service == "authenticationV2" || bcm._inProgressQueue[c].service == "identity"))
{
if (data.sessionId)
{
bcm._sessionId = data.sessionId;
}
if (data.profileId)
{
bcm.authentication.profileId = data.profileId;
}
if (data.switchToAppId)
{
bcm._appId = data.switchToAppId;
bcm._secret = bcm._secretMap[data.switchToAppId];
}
}
if (bcm._inProgressQueue[c].service == "playerState" &&
(bcm._inProgressQueue[c].operation == "LOGOUT" || bcm._inProgressQueue[c].operation == "FULL_RESET"))
{
bcm.stopHeartBeat();
bcm._isAuthenticated = false;
bcm._sessionId = "";
bcm.authentication.profileId = "";
}
else if (bcm._inProgressQueue[c].operation == "AUTHENTICATE")
{
bcm._isAuthenticated = true;
if (data.hasOwnProperty("playerSessionExpiry"))
{
bcm._idleTimeout = data.playerSessionExpiry * 0.85;
}
else
{
bcm._idleTimeout = 30;
}
if(data.hasOwnProperty("maxKillCount"))
{
bcm._killSwitchThreshold = data.maxKillCount;
}
bcm.resetErrorCache();
bcm.startHeartBeat();
}
if (bcm._rewardCallback)
{
var rewards = null;
if (data &&
bcm._inProgressQueue[c].service &&
bcm._inProgressQueue[c].operation)
{
if (bcm._inProgressQueue[c].service == "authenticationV2" &&
bcm._inProgressQueue[c].operation == "AUTHENTICATE")
{
bcm.resetErrorCache();
if (data.rewards && data.rewards.rewards)
{
rewards = data.rewards;
}
}
else if ((bcm._inProgressQueue[c].service == "playerStatistics" && bcm._inProgressQueue[c].operation == "UPDATE") ||
(bcm._inProgressQueue[c].service == "playerStatisticsEvent" && (bcm._inProgressQueue[c].operation == "TRIGGER" || bcm._inProgressQueue[c].operation ==
"TRIGGER_MULTIPLE")))
{
if (data.rewards)
{
rewards = data;
}
}
if (rewards)
{
bcm._rewardCallback(rewards);
}
}
}
}
else
{
var statusCode = messages[c].status;
var reasonCode = messages[c].reason_code;
if (reasonCode === 40303 ||
reasonCode === 40304 ||
reasonCode === 40356)
{
bcm.stopHeartBeat();
bcm._isAuthenticated = false;
bcm._sessionID = "";
// cache error if session related
bcm._statusCodeCache = statusCode;
bcm._reasonCodeCache = reasonCode;
bcm._statusMessageCache = messages[c].status_message;
}
bcm.debugLog("STATUSCodes: " + bcm.statusCodes.CLIENT_NETWORK_ERROR);
bcm.updateKillSwitch(bcm._inProgressQueue[c].service, bcm._inProgressQueue[c].operation, statusCode)
}
if (callback)
{
callback(messages[c]);
}
}
var events = response["events"];
if (events && bcm._eventCallback)
{
for (var c = 0; c < events.length; ++c)
{
var eventsJson = {
events: events
};
bcm._eventCallback(eventsJson);
}
}
}
bcm.fakeErrorResponse = function(statusCode, reasonCode, message)
{
var responses = [];
var response = {};
response.status = statusCode;
response.reason_code = reasonCode;
response.status_message = message;
response.severity = "ERROR";
for (var i = 0; i < bcm._inProgressQueue.length; i++)
{
responses.push(response);
}
bcm.handleSuccessResponse(
{
"responses": responses
});
}
bcm.setHeader = function(xhr)
{
var sig = CryptoJS.MD5(bcm._jsonedQueue + bcm._secret);
xhr.setRequestHeader('X-SIG', sig);
xhr.setRequestHeader('X-APPID', bcm._appId);
}
bcm.retry = function()
{
if (bcm._retry <= bcm._packetTimeouts.length)
{
bcm._retry++;
bcm.debugLog("Retry # " + bcm._retry.toString(), false);
if (bcm._retry === 1)
{
bcm.debugLog("Retrying right away", false);
bcm.performQuery();
}
else
{
bcm.debugLog("Waiting for " + bcm._packetTimeouts[bcm._retry - 1] + " sec...", false);
setTimeout(bcm.performQuery, bcm._packetTimeouts[bcm._retry - 1] * 1000);
}
}
else
{
bcm.debugLog("Failed after " + bcm._retry + " retries.", true);
if ((bcm._errorCallback != undefined) &&
(typeof bcm._errorCallback == 'function'))
{
bcm._errorCallback(errorThrown);
}
bcm.fakeErrorResponse(bcm.statusCodes.CLIENT_NETWORK_ERROR, bcm.reasonCodes.CLIENT_NETWORK_ERROR_TIMEOUT, "Request timed out");
bcm._requestInProgress = false;
// Now call bcm.processQueue again if there is more data...
bcm.processQueue();
}
}
bcm.handleResponse = function (status, response) {
clearTimeout(bcm.xml_timeoutId);
bcm.xml_timeoutId = null;
bcm.debugLog("Response Status: " + status);
bcm.debugLog("Response: " + JSON.stringify(response));
if (status == 200) {
bcm.handleSuccessResponse(response);
bcm._requestInProgress = false;
bcm.processQueue();
}
else if (status == 502 || status == 503 || status == 504) {
bcm.debugLog("packet in progress", false);
bcm.retry();
return;
}
else {
try {
var errorResponse = response;
if (errorResponse["reason_code"]) {
reasonCode = errorResponse["reason_code"];
}
if (errorResponse["status_message"]) {
statusMessage = errorResponse["status_message"];
}
else {
statusMessage = response;
}
}
catch (e) {
reasonCode = 0;
statusMessage = response;
}
var errorMessage = response;
bcm.debugLog("Failed", true);
if ((bcm._errorCallback != undefined) &&
(typeof bcm._errorCallback == 'function')) {
bcm._errorCallback(errorMessage);
}
if (!errorMessage || errorMessage == "") errorMessage = "Unknown error. Did you lose internet connection?";
bcm.fakeErrorResponse(bcm.statusCodes.CLIENT_NETWORK_ERROR, reasonCode,
errorMessage);
}
}
bcm.performQuery = function()
{
//> REMOVE IF K6
clearTimeout(bcm.xml_timeoutId);
//> END
bcm.xml_timeoutId = null;
bcm._requestInProgress = true;
var xmlhttp;
if (window.XMLHttpRequest)
{
// code for IE7+, Firefox, Chrome, Opera, Safari
//> ADD IF K6
//+ xmlhttp = window.XMLHttpRequest;
//> END
//> REMOVE IF K6
xmlhttp = new XMLHttpRequest();
//> END
}
else
{
// code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.requestId = ++bcm._requestId;
//> REMOVE IF K6
xmlhttp.ontimeout_bc = function()
{
if (xmlhttp.readyState < 4)
{
xmlhttp.hasTimedOut = true;
xmlhttp.abort();
xmlhttp.hasTimedOut = null;
bcm.xml_timeoutId = null;
bcm.debugLog("timeout", false);
bcm.retry();
}
}
xmlhttp.onreadystatechange = function () {
if (xmlhttp.hasTimedOut || xmlhttp.requestId != bcm._requestId) {
return;
}
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
bcm.handleResponse(xmlhttp.status, JSON.parse(xmlhttp.responseText))
}
};
bcm.xml_timeoutId = setTimeout(xmlhttp.ontimeout_bc, bcm._packetTimeouts[0] * 1000);
xmlhttp.open("POST", bcm._dispatcherUrl, true);
xmlhttp.setRequestHeader("Content-type", "application/json");
var sig = CryptoJS.MD5(bcm._jsonedQueue + bcm._secret);
xmlhttp.setRequestHeader("X-SIG", sig);
xmlhttp.setRequestHeader('X-APPID', bcm._appId);
if (bcm._compressionEnabled) {
bcm.compressRequest(bcm._jsonedQueue)
.then(function (compressedData) {
fetch(bcm._dispatcherUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-SIG": sig,
"X-APPID": bcm._appId,
"Content-Encoding": "gzip"
},
body: compressedData
})
.then(function (response) {
var status = response.status;
return response.arrayBuffer().then(function (buffer) {
return { status: status, buffer: buffer };
});
})
.then(function (result) {
var responseStatus = result.status;
var jsonString = new TextDecoder().decode(result.buffer);
var responseJSON = JSON.parse(jsonString);
bcm.handleResponse(responseStatus, responseJSON)
})
.catch(function (error) {
console.error(error);
});
})
.catch(function (err) {
console.error("Compression failed:", err);
console.log("Sending request without compression...");
xmlhttp.send(bcm._jsonedQueue);
});
}
else{
xmlhttp.send(bcm._jsonedQueue);
}
//> END
// Set a timeout. Some implementation doesn't implement the XMLHttpRequest timeout and ontimeout (Including nodejs and chrome!)
//> ADD IF K6
//+ let sig = CryptoJS.md5(bcm._jsonedQueue + bcm._secret, 'hex');
//+ let _jsonedQueue = JSON.parse(bcm._jsonedQueue);
//+ let params = {
//+ cookies: { my_cookie: _jsonedQueue.messages[0].service+"."+_jsonedQueue.messages[0].operation },
//+ headers: {
//+ 'Content-Type': 'application/json',
//+ 'X-SIG': sig,
//+ 'X_APPID': bcm._appId
//+ },
//+ // redirects: 5,
//+ tags: {
//+ api: _jsonedQueue.messages[0].service+"."+_jsonedQueue.messages[0].operation,
//+ name: _jsonedQueue.messages[0].service+"."+_jsonedQueue.messages[0].operation,
//+ url: bcm._dispatcherUrl
//+ },
//+ timeout: bcm._packetTimeouts[0] * 1000
//+ };
//+ let res = xmlhttp.post(bcm._dispatcherUrl, bcm._jsonedQueue, params);
//+ responseG = res;
//+
//+ bcm.debugLog("response status : " + res.status);
//+ bcm.debugLog("response : " + res.body);
//+
//+ if (res.status == 200)
//+ {
//+ var response = JSON.parse(res.body);
//+
//+ bcm.handleSuccessResponse(response, res);
//+ bcm.processQueue();
//+ }
//+ else if (res.status == 503)
//+ {
//+ bcm.debugLog("packet in progress", false);
//+ bcm.retry();
//+ return;
//+ }
//+ else
//+ {
//+ try
//+ {
//+ var errorResponse = JSON.parse(res.body);
//+ if (errorResponse["reason_code"])
//+ {
//+ reasonCode = errorResponse["reason_code"];
//+ }
//+ if (errorResponse["status_message"])
//+ {
//+ statusMessage = errorResponse["status_message"];
//+ }
//+ else
//+ {
//+ statusMessage = res.body;
//+ }
//+ }
//+ catch (e)
//+ {
//+ reasonCode = 0;
//+ statusMessage = res.body;
//+ }
//+
//+ var errorMessage = res.body;
//+ bcm.debugLog("Failed", true);
//+
//+ if ((bcm._errorCallback != undefined) &&
//+ (typeof bcm._errorCallback == 'function'))
//+ {
//+ bcm._errorCallback(errorMessage, res);
//+ }
//+ }
//> END
}
bcm.processQueue = function()
{
if (bcm._sendQueue.length > 0)
{
// Uncomment if you want to debug bundles
// bcm.debugLog("---BUNDLE---: " + JSON.stringify(bcm._sendQueue));
bcm._inProgressQueue = [];
var itemsProcessed;
for (itemsProcessed = 0; itemsProcessed < bcm._sendQueue.length; ++itemsProcessed)
{
var message = bcm._sendQueue[itemsProcessed];
if (message.operation == "END_BUNDLE_MARKER")
{
if (bcm._inProgressQueue.length == 0)
{
// ignore bundle markers at the beginning of the bundle
continue;
}
else
{
// end the message bundle
++itemsProcessed;
break;
}
}
bcm._inProgressQueue.push(message);
}
bcm._sendQueue.splice(0, itemsProcessed);
if (bcm._inProgressQueue.length <= 0)
{
return;
}
bcm._jsonedQueue = JSON.stringify(
{
messages: bcm._inProgressQueue,
gameId: bcm._appId,
sessionId: bcm._sessionId,
packetId: bcm._packetId++
});
localStorage.setItem("lastPacketId", bcm._packetId);
if(bcm._killSwitchEngaged)
{
bcm.fakeErrorResponse(bcm.statusCodes.CLIENT_NETWORK_ERROR,
bcm.reasonCodes.CLIENT_DISABLED,
"Client disabled due to repeated errors from a single API call");
return;
}
if (!bcm._isAuthenticated)
{
var isAuth = false;
for (i = 0; i < bcm._inProgressQueue.length; i++)
{
if (bcm._inProgressQueue[i].operation == "AUTHENTICATE" ||
bcm._inProgressQueue[i].operation == "RESET_EMAIL_PASSWORD" ||
bcm._inProgressQueue[i].operation == "RESET_EMAIL_PASSWORD_ADVANCED" ||
bcm._inProgressQueue[i].operation == "GET_SERVER_VERSION")
{
isAuth = true;
break;
}
}
if (!isAuth)
{
bcm.fakeErrorResponse(bcm._statusCodeCache, bcm._reasonCodeCache, bcm._statusMessageCache);
return;
}
}
bcm._retry = 0;
bcm.performQuery();
}
}
}
//> REMOVE IF K6
BrainCloudManager.apply(window.brainCloudManager = window.brainCloudManager || {});
//> END
/**
* @status complete
*/
function BCAbTest() {
var bc = this;
bc.abtests = {};
bc.abtests.loadABTestData = function (dataUrl, callback) {
console.log("called loadABTestData(" + dataUrl + ",callback)");
// Retrieve AB Test data from AppServer S3 service.
jQuery.ajax({
timeout: 15000,
url: dataUrl,
type: "POST",
contentType: "application/json",
dataType: "json",
data: JSON.stringify({})
}).done(function (response) {
// success...
console.log("loadABTestData() - GOT: " + JSON.stringify(response));
if (response != null) {
abTestData = response;
}
if (callback) {
callback();
}
}).fail(function (jqXhr, textStatus, errorThrown) {
// failure...
console.log("loadABTestData() - FAILED: " + jqXhr + " " + textStatus + " " + errorThrown);
});
};
bc.abtests.getABTest = function (abTestingId, abTestName) {
console.log("called getABTest(" + abTestingId + "," + abTestName + ").");
// Process the AB Test data and determine if an active test exists that satisfies the supplied parameters.
for (var x = 0; x < abTestData.ab_tests.length; x++) {
if (abTestData.ab_tests[x].name == abTestName && abTestData.ab_tests[x].active == "true") {
for (var y = 0; y < abTestData.ab_tests[x].data.length; y++) {
// Check the ab_testing_id against the range defined in the test.
var minId = abTestData.ab_tests[x].data[y].min;
var maxId = abTestData.ab_tests[x].data[y].max;
if (abTestingId >= minId && abTestingId <= maxId) {
console.log("getABTest() - Found AB test '" + abTestName + ":" + abTestData.ab_tests[x].data[y].name + "' for abTestingId '" + abTestingId + "' in range '" + minId + "' to '" + maxId + "'.");
return abTestData.ab_tests[x].data[y].name;
}
}
}
}
console.log("getABTest() - Could not find an '" + abTestName + "' AB test for abTestingId '" + abTestingId + "'.");
return null;
};
bc.abtests.pushABTestResult = function (abTestingId, abTestName, abSelection, result) {
console.log("called pushABTestResult(" + abTestingId + "," + abTestName + "," + abSelection + "," + result + ").");
/*
// Push the AB Test result to MixPanel Analytics.
mixpanel.track("ABTest", {
'platform': 'javascript',
'abTestingId': abTestingId,
'abTestName': abTestName,
'abSelection': abSelection,
'result': result
});*/
};
bc.abtests.setABTestingId = function (abTestingId) {
bc.brainCloudManager.setABTestingId(abTestingId);
};
bc.abtests.getABTestingId = function () {
return bc.brainCloudManager.getABTestingId();
};
}
//> REMOVE IF K6
BCAbTest.apply(window.brainCloudClient = window.brainCloudClient || {});
//> END
function BCAppStore() {
var bc = this;
bc.appStore = {};
bc.SERVICE_APP_STORE = "appStore";
bc.appStore.OPERATION_CACHE_PURCHASE_PAYLOAD_CONTEXT = "CACHE_PURCHASE_PAYLOAD_CONTEXT";
bc.appStore.OPERATION_FINALIZE_PURCHASE = "FINALIZE_PURCHASE";
bc.appStore.OPERATION_GET_ELIGIBLE_PROMOTIONS = "ELIGIBLE_PROMOTIONS";
bc.appStore.OPERATION_GET_SALES_INVENTORY = "GET_INVENTORY";
bc.appStore.OPERATION_REFRESH_PROMOTIONS = "REFRESH_PROMOTIONS";
bc.appStore.OPERATION_START_PURCHASE = "START_PURCHASE";
bc.appStore.OPERATION_VERIFY_PURCHASE = "VERIFY_PURCHASE";
/**
* Caches a payload context to retreive as fallback if the store API cannot provide the payload.
*
* Service Name - AppStore
* Service Operation - CACHE_PURCHASE_PAYLOAD_CONTEXT
*
* @param {string} storeId The store platform. Ex: "googlePlay".
* @param {string} iapId In-app product id.
* @param {string} payload The payload string to cache.
* @param {function} callback The function to be invoked when the server response is received.
*/
bc.appStore.cachePurchasePayloadContext = function (storeId, iapId, payload, callback) {
var data = {
storeId: storeId,
iapId: iapId,
payload: payload
};
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_APP_STORE,
operation: bc.appStore.OPERATION_CACHE_PURCHASE_PAYLOAD_CONTEXT,
data: data,
callback: callback
});
};
/**
* Verifies that purchase was properly made at the store.
*
* Service Name - AppStore
* Service Operation - VerifyPurchase
*
* @param storeId The store platform. Valid stores are:
* - itunes
* - facebook
* - appworld
* - steam
* - windows
* - windowsPhone
* - googlePlay
* @param receiptData the specific store data required
* @param callback The method to be invoked when the server response is received
*/
bc.appStore.verifyPurchase = function(storeId, receiptData, callback) {
var message = {
storeId: storeId,
receiptData: receiptData
};
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_APP_STORE,
operation: bc.appStore.OPERATION_VERIFY_PURCHASE,
data: message,
callback: callback
});
};
/**
* Returns the eligible promotions for the player.
*
* Service Name - AppStore
* Service Operation - EligiblePromotions
*
* @param callback The method to be invoked when the server response is received
*/
bc.appStore.getEligiblePromotions = function(callback) {
var message = {
};
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_APP_STORE,
operation: bc.appStore.OPERATION_GET_ELIGIBLE_PROMOTIONS,
data: message,
callback: callback
});
};
/**
* Method gets the active sales inventory for the passed-in
* currency type.
*
* Service Name - AppStore
* Service Operation - GetInventory
*
* @param storeId The store platform. Valid stores are:
* - itunes
* - facebook
* - appworld
* - steam
* - windows
* - windowsPhone
* - googlePlay
* @param userCurrency The currency type to retrieve the sales inventory for.
* @param callback The method to be invoked when the server response is received
*/
bc.appStore.getSalesInventory = function(storeId, userCurrency, callback) {
bc.appStore.getSalesInventoryByCategory(storeId, userCurrency, null, callback);
};
/**
* Method gets the active sales inventory for the passed-in
* currency type.
*
* Service Name - AppStore
* Service Operation - GetInventory
*
* @param storeId The store platform. Valid stores are:
* - itunes
* - facebook
* - appworld
* - steam
* - windows
* - windowsPhone
* - googlePlay
* @param userCurrency The currency type to retrieve the sales inventory for.
* @param category The product category
* @param callback The method to be invoked when the server response is received
*/
bc.appStore.getSalesInventoryByCategory = function(storeId, userCurrency, category, callback) {
var message = {
storeId: storeId,
category: category,
priceInfoCriteria: {
userCurrency: userCurrency
}
};
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_APP_STORE,
operation: bc.appStore.OPERATION_GET_SALES_INVENTORY,
data: message,
callback: callback
});
};
/**
* Start A Two Staged Purchase Transaction
*
* Service Name - AppStore
* Service Operation - StartPurchase
*
* @param storeId The store platform. Valid stores are:
* - itunes
* - facebook
* - appworld
* - steam
* - windows
* - windowsPhone
* - googlePlay
* @param purchaseData specific data for purchasing 2 staged purchases
* @param callback The method to be invoked when the server response is received
*/
bc.appStore.startPurchase = function(storeId, purchaseData, callback) {
var message = {
storeId: storeId,
purchaseData: purchaseData
};
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_APP_STORE,
operation: bc.appStore.OPERATION_START_PURCHASE,
data: message,
callback: callback
});
};
/**
* Finalize A Two Staged Purchase Transaction
*
* Service Name - AppStore
* Service Operation - FinalizePurchase
*
* @param storeId The store platform. Valid stores are:
* - itunes
* - facebook
* - appworld
* - steam
* - windows
* - windowsPhone
* - googlePlay
* @param transactionId the transactionId returned from start Purchase
* @param transactionData specific data for purchasing 2 staged purchases
* @param callback The method to be invoked when the server response is received
*/
bc.appStore.finalizePurchase = function(storeId, transactionId, transactionData, callback) {
var message = {
storeId: storeId,
transactionId: transactionId,
transactionData: transactionData
};
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_APP_STORE,
operation: bc.appStore.OPERATION_FINALIZE_PURCHASE,
data: message,
callback: callback
});
};
/**
* Returns up-to-date eligible 'promotions' for the user and
* a 'promotionsRefreshed' flag indicating whether the user's promotion info required refreshing.
*
* Service Name - AppStore
* Service Operation - RefreshPromotions
*
* @param callback The method to be invoked when the server response is received
*/
bc.appStore.refreshPromotions = function(callback) {
var message = {
};
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_APP_STORE,
operation: bc.appStore.OPERATION_REFRESH_PROMOTIONS,
data: message,
callback: callback
});
};
}
//> REMOVE IF K6
BCAppStore.apply(window.brainCloudClient = window.brainCloudClient || {});
//> END
function BCAsyncMatch() {
var bc = this;
bc.asyncMatch = {};
bc.SERVICE_ASYNC_MATCH = "asyncMatch";
bc.asyncMatch.OPERATION_SUBMIT_TURN = "SUBMIT_TURN";
bc.asyncMatch.OPERATION_UPDATE_SUMMARY = "UPDATE_SUMMARY";
bc.asyncMatch.OPERATION_ABANDON = "ABANDON";
bc.asyncMatch.OPERATION_COMPLETE = "COMPLETE";
bc.asyncMatch.OPERATION_CREATE = "CREATE";
bc.asyncMatch.OPERATION_READ_MATCH = "READ_MATCH";
bc.asyncMatch.OPERATION_READ_MATCH_HISTORY = "READ_MATCH_HISTORY";
bc.asyncMatch.OPERATION_FIND_MATCHES = "FIND_MATCHES";
bc.asyncMatch.OPERATION_FIND_MATCHES_COMPLETED = "FIND_MATCHES_COMPLETED";
bc.asyncMatch.OPERATION_DELETE_MATCH = "DELETE_MATCH";
bc.asyncMatch.OPERATION_ABANDON_MATCH_WITH_SUMMARY_DATA = "ABANDON_MATCH_WITH_SUMMARY_DATA";
bc.asyncMatch.OPERATION_COMPLETE_MATCH_WITH_SUMMARY_DATA = "COMPLETE_MATCH_WITH_SUMMARY_DATA";
bc.asyncMatch.OPERATION_UPDATE_MATCH_STATE_CURRENT_TURN = "UPDATE_MATCH_STATE_CURRENT_TURN";
/**
* Creates an instance of an asynchronous match.
*
* Service Name - AsyncMatch
* Service Operation - Create
*
* @param opponentIds JSON string identifying the opponent platform and id for this match.
*
* Platforms are identified as:
* BC - a brainCloud profile id
* FB - a Facebook id
*
* An exmaple of this string would be:
* [
* {
* "platform": "BC",
* "id": "some-braincloud-profile"
* },
* {
* "platform": "FB",
* "id": "some-facebook-id"
* }
* ]
*
* @param pushNotificationMessage Optional push notification message to send to the other party.
* Refer to the Push Notification functions for the syntax required.
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.createMatch = function(opponentIds, pushNotificationMessage, callback) {
var data = {
players: opponentIds
};
if (pushNotificationMessage) {
data["pushContent"] = pushNotificationMessage;
}
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_CREATE,
data: data,
callback: callback
});
};
/**
* Creates an instance of an asynchronous match with an initial turn.
*
* Service Name - AsyncMatch
* Service Operation - Create
*
* @param opponentIds JSON string identifying the opponent platform and id for this match.
*
* Platforms are identified as:
* BC - a brainCloud profile id
* FB - a Facebook id
*
* An exmaple of this string would be:
* [
* {
* "platform": "BC",
* "id": "some-braincloud-profile"
* },
* {
* "platform": "FB",
* "id": "some-facebook-id"
* }
* ]
*
* @param matchState JSON string blob provided by the caller
* @param pushNotificationMessage Optional push notification message to send to the other party.
* Refer to the Push Notification functions for the syntax required.
* @param nextPlayer Optionally, force the next player player to be a specific player
* @param summary Optional JSON string defining what the other player will see as a summary of the game when listing their games
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.createMatchWithInitialTurn = function(opponentIds, matchState,
pushNotificationMessage, nextPlayer, summary, callback) {
var data = {
players: opponentIds
};
if (matchState) {
data["matchState"] = matchState;
}
else data["matchState"] = {};
if (pushNotificationMessage) {
data["pushContent"] = pushNotificationMessage;
}
if (nextPlayer) {
data["status"] = { currentPlayer: nextPlayer };
}
if (summary) {
data["summary"] = summary;
}
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_CREATE,
data: data,
callback: callback
});
};
/**
* Returns the current state of the given match.
*
* Service Name - AsyncMatch
* Service Operation - ReadMatch
*
* @param ownerId Match owner identifier
* @param matchId Match identifier
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.readMatch = function(ownerId, matchId, callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_READ_MATCH,
data: {
ownerId: ownerId,
matchId: matchId
},
callback: callback
});
};
/**
* Submits a turn for the given match.
*
* Service Name - AsyncMatch
* Service Operation - SubmitTurn
*
* @param ownerId Match owner identfier
* @param matchId Match identifier
* @param version Game state version to ensure turns are submitted once and in order
* @param matchState JSON string provided by the caller
* @param pushNotificationMessage Optional push notification message to send to the other party.
* Refer to the Push Notification functions for the syntax required.
* @param nextPlayer Optionally, force the next player player to be a specific player
* @param summary Optional JSON string that other players will see as a summary of the game when listing their games
* @param statistics Optional JSON string blob provided by the caller
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.submitTurn = function(ownerId, matchId, version, matchState,
pushNotificationMessage, nextPlayer, summary, statistics, callback) {
var data = {
ownerId: ownerId,
matchId: matchId,
version: version
};
if (matchState) {
data["matchState"] = matchState;
}
else data["matchState"] = {};
if (nextPlayer) {
data["status"] = { currentPlayer: nextPlayer };
}
if (summary) {
data["summary"] = summary;
}
if (statistics) {
data["statistics"] = statistics;
}
if(pushNotificationMessage){
data["pushContent"] = pushNotificationMessage;
}
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_SUBMIT_TURN,
data: data,
callback: callback
});
};
/**
* Allows the current player (only) to update Summary data without having to submit a whole turn.
*
* Service Name - AsyncMatch
* Service Operation - UpdateMatchSummary
*
* @param ownerId Match owner identfier
* @param matchId Match identifier
* @param version Game state version to ensure turns are submitted once and in order
* @param summary JSON string that other players will see as a summary of the game when listing their games
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.updateMatchSummaryData = function(ownerId, matchId, version, summary, callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_UPDATE_SUMMARY,
data: {
ownerId: ownerId,
matchId: matchId,
version: version,
summary: summary
},
callback: callback
});
};
/**
* Marks the given match as abandoned.
*
* Service Name - AsyncMatch
* Service Operation - Abandon
*
* @param ownerId Match owner identifier
* @param matchId Match identifier
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.abandonMatch = function(ownerId, matchId, callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_ABANDON,
data: {
ownerId: ownerId,
matchId: matchId
},
callback: callback
});
};
/**
* Marks the given match as complete.
*
* Service Name - AsyncMatch
* Service Operation - Complete
*
* @param ownerId Match owner identifier
* @param matchId Match identifier
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.completeMatch = function(ownerId, matchId, callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_COMPLETE,
data: {
ownerId: ownerId,
matchId: matchId
},
callback: callback
});
};
/**
* Returns the match history of the given match.
*
* Service Name - AsyncMatch
* Service Operation - ReadMatchHistory
*
* @param ownerId Match owner identifier
* @param matchId Match identifier
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.readMatchHistory = function(ownerId, matchId, callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_READ_MATCH_HISTORY,
data: {
ownerId: ownerId,
matchId: matchId
},
callback: callback
});
};
/**
* Returns all matches that are NOT in a COMPLETE state for which the player is involved.
*
* Service Name - AsyncMatch
* Service Operation - FindMatches
*
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.findMatches = function(callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_FIND_MATCHES,
callback: callback
});
};
/**
* Returns all matches that are in a COMPLETE state for which the player is involved.
*
* Service Name - AsyncMatch
* Service Operation - FindMatchesCompleted
*
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.findCompleteMatches = function(callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_FIND_MATCHES_COMPLETED,
callback: callback
});
};
/**
* Removes the match and match history from the server. DEBUG ONLY, in production it is recommended
* the user leave it as completed.
*
* Service Name - AsyncMatch
* Service Operation - Delete
*
* @param ownerId Match owner identifier
* @param matchId Match identifier
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.deleteMatch = function(ownerId, matchId, callback) {
bc.brainCloudManager.sendRequest({
service: bc.SERVICE_ASYNC_MATCH,
operation: bc.asyncMatch.OPERATION_DELETE_MATCH,
data: {
ownerId: ownerId,
matchId: matchId
},
callback: callback
});
};
/**
* Marks the given match as complete. This call can send a notification message.
*
* Service Name - AsyncMatch
* Service Operation - CompleteMatchWithSummaryData
*
* @param ownerId Match owner identifier
* @param matchId Match identifier
* @param pushContent what to push the
* @param summary json summary
* @param callback Optional instance of IServerCallback to call when the server response is received.
*/
bc.asyncMatch.completeMatchWithSummaryData = function(ownerId, matchId, pushContent, summary, callback) {
bc.brainCloudManager.sendRequest({
ser