brainclouds2s
Version:
brianCloud S2S module
362 lines (320 loc) • 10.7 kB
JavaScript
;
var https = require('https')
var util = require('util')
var RTT = require('./brainclouds2s-rtt')
// Constants
const SERVER_SESSION_EXPIRED = 40365 // Error code for expired session
const HEARTBEAT_INTERVALE_MS = 60 * 30 * 1000 // 30 minutes heartbeat interval
const STATE_DISCONNECTED = 0
const STATE_AUTHENTICATING = 1
const STATE_CONNECTED = 2
// Execute the S2S request
function s2sRequest(context, json, callback) {
var postData = JSON.stringify(json)
if (context.logEnabled) {
console.log(`[S2S SEND ${context.appId}] ${postData}`)
}
var options = {
host: context.url,
path: '/s2sdispatcher',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': (new util.TextEncoder().encode(postData)).length
}
}
var req = https.request(options, res => {
var data = ''
// A chunk of data has been recieved.
res.on('data', chunk => {
data += chunk
})
// The whole response has been received. Print out the result.
res.on('end', () => {
if (context.logEnabled) {
console.log(`[S2S RECV ${context.appId}] ${data}`)
}
let responseData = null;
if (data) {
try {
responseData = JSON.parse(data);
}
catch (error) {
console.log(`[S2S Error parsing response data ${context.appId}] ${error}`)
}
}
callCallback(context, responseData, callback)
})
}).on("error", err => {
if (context.logEnabled) {
console.log(`[S2S Error making request ${context.appId}] ${err.message}`)
}
callCallback(context, null, callback)
})
// write data to request body
req.write(postData)
req.end()
}
function startHeartbeat(context) {
stopHeartbeat(context)
context.heartbeatInternalId = setInterval(() => {
if (context.logEnabled) {
console.log("Heartbeat")
}
request(context, {
service: "heartbeat",
operation: "HEARTBEAT"
}, (context, data) => {
if (!(data && data.status === 200)) {
exports.disconnect(context)
}
})
}, HEARTBEAT_INTERVALE_MS)
}
function stopHeartbeat(context) {
if (context.heartbeatInternalId) {
clearInterval(context.heartbeatInternalId)
context.heartbeatInternalId = null
}
}
function authenticateInternal(context, callback) {
let packetId = 0
context.state = STATE_AUTHENTICATING;
let json = {
packetId: packetId,
messages: [
{
service: "authenticationV2",
operation: "AUTHENTICATE",
data: {
appId: context.appId,
serverName: context.serverName,
serverSecret: context.serverSecret
}
}
]
}
s2sRequest(context, json, (context, data) => {
if (data && data.messageResponses && data.messageResponses.length > 0) {
let message = data.messageResponses[0]
if (data.messageResponses[0].status === 200) {
context.state = STATE_CONNECTED
context.packetId = data.packetId + 1
context.sessionId = message.data.sessionId
// Start heartbeat
startHeartbeat(context)
}
else {
failAllRequests(context, message);
}
callCallback(context, message, callback)
}
else {
failAllRequests(context, null);
exports.disconnect(context)
callCallback(context, null, callback)
}
})
}
function reAuth(context) {
authenticateInternal(context, (context, data) => {
if (data) {
context.requestQueue.push({ json: json, callback: callback });
if (context.requestQueue.length > 0) {
let nextRequest = context.requestQueue[0];
request(context, nextRequest.json, nextRequest.callback)
}
}
})
}
function queueRequest(context, json, callback) {
context.requestQueue.push({ json: json, callback: callback });
// If only 1 in the queue, then send the request immediately
// Also make sure we're not in the process of authenticating
if (context.requestQueue.length == 1 &&
context.state != STATE_AUTHENTICATING) {
request(context, json, callback)
}
}
function failAllRequests(context, message) {
// Callback to all queued messages
let requestQueue = context.requestQueue;
context.requestQueue = [];
for (let request in context.requestQueue) {
if (request.callback) {
callCallback(context, message, request.callback);
}
}
}
function request(context, json, callback) {
let packet = {
packetId: context.packetId,
sessionId: context.sessionId,
messages: [json]
}
context.packetId++
s2sRequest(context, packet, (context, data) => {
if (data && data.status != 200 && data.reason_code === SERVER_SESSION_EXPIRED && context.retryCount < 3) {
stopHeartbeat(context)
context.authenticated = false
context.retryCount++
context.packetId = 0
context.sessionId = null
reAuth(context);
return
}
if (data && data.messageResponses && data.messageResponses.length > 0) {
callCallback(context, data.messageResponses[0], callback)
}
else {
// Error that has no packets
callCallback(context, data, callback)
}
// This request is complete and safe to remove from queue after invoking callback
context.requestQueue.splice(0, 1); // Remove this request from the queue
context.retryCount = 0;
// Do next request in queue
if (context.requestQueue.length > 0) {
let nextRequest = context.requestQueue[0];
request(context, nextRequest.json, nextRequest.callback)
}
})
}
/**
* Invoke a given callback.
* @param context S2S context object
* @param data Content object to be sent
* @param callback Function to be executed
*/
function callCallback(context, data, callback) {
if (callback != null) {
try {
callback(context, data);
}
catch (error) {
console.log(`[S2S Callback Error ${context.appId}] ${error}`)
}
}
}
/*
* Create a new S2S context
* @param appId Application ID
* @param serverName Server name
* @param serverSecret Server secret key
* @param url The server url to send the request to. Defaults to the default
* brainCloud portal
* @param autoAuth If sets to true, the context will authenticate on the
* first request if it's not already. Otherwise,
* authenticate() or authenticateSync() must be called
* successfully first before doing requests. WARNING: This
* used to be implied true.
*
* It is recommended to put this to false, manually
* call authenticate, and wait for a successful response
* before proceeding with other requests.
* @return A new S2S context
*/
exports.init = (appId, serverName, serverSecret, url, autoAuth) => {
if (!url) {
url = "api.braincloudservers.com"
}
return {
url: url,
appId: appId,
serverName: serverName,
serverSecret: serverSecret,
logEnabled: false,
state: STATE_DISCONNECTED,
packetId: 0,
sessionId: null,
heartbeatInternalId: null,
autoAuth: autoAuth ? true : false,
retryCount: 0,
requestQueue: []
}
}
/*
* Force disconnect a S2S session
* @param context S2S context object returned by init
*/
exports.disconnect = context => {
stopHeartbeat(context)
context.state = STATE_DISCONNECTED
context.retryCount = 0
context.packetId = 0
context.sessionId = null
context.requestQueue = []
}
/*
* Set wether S2S messages and errors are logged to the console
* @param context S2S context object returned by init
* @param enabled Will log if true. Default false
*/
exports.setLogEnabled = (context, enabled) => {
context.logEnabled = enabled
}
/*
* Authenticate
* @param context S2S context object returned by init
* @param callback Callback function with signature: (context, result)
*/
exports.authenticate = (context, callback) => {
authenticateInternal(context, callback)
}
/*
* Send an S2S request
* @param context S2S context object returned by init
* @param json Content object to be sent
* @param callback Callback function with signature: (context, result)
*/
exports.request = (context, json, callback) => {
if (context.state == STATE_DISCONNECTED && context.autoAuth) {
authenticateInternal(context, (context, result) => {
if (context.state == STATE_CONNECTED &&
result && result.status == 200) {
if (context.requestQueue.length > 0) {
let nextRequest = context.requestQueue[0];
request(context, nextRequest.json, nextRequest.callback)
}
}
});
}
queueRequest(context, json, callback);
}
/**
* Attempts to establish an RTT connection to the brainCloud servers.
* @param {*} context object containing session data (appId, serverName, etc.)
* @param {*} success function to be invoked when an RTT connection has been established
* @param {*} failure function to be invoked if an RTT connection is not established
* @returns if RTT is already enabled
*/
exports.enableRTT = (context, success, failure) => {
RTT.enableRTT(context, success, failure)
}
/**
* Disables the RTT connection.
* @returns if RTT is not enabled
*/
exports.disableRTT = () => {
RTT.disableRTT()
}
/**
* Returns whether or not RTT is enabled.
* @returns True if RTT is enabled
*/
exports.rttIsEnabled = () => {
return RTT.rttIsEnabled()
}
/**
* Registers a callback for all RTT services
* @param {*} callback function to be invoked when receiving RTT updates
*/
exports.registerRTTRawCallback = (callback) => {
RTT.registerRTTRawCallback(callback)
}
/**
* Deregisters the RTT callback.
*/
exports.deregisterRTTRawCallback = () => {
RTT.deregisterRTTRawCallback()
}