Sign In

brainclouds2s

Package Overview
Dependencies
Maintainers
7
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

brainclouds2s - npm Package Compare versions

Comparing version
5.9.0
to
6.0.0
+486
brainclouds2s-globalfilev3.js
'use strict';
//----------------------------------------------------
// brainCloud client source code
// Copyright 2026 bitHeads, inc.
//----------------------------------------------------
var https = require('https');
var brainclouds2s = require('./brainclouds2s');
/**
* S2S service for brainCloud Global File V3 operations.
*
* Usage:
* const S2S = require('./brainclouds2s');
* const GFV3 = require('./brainclouds2s-globalfilev3');
*
* let context = S2S.init(appId, serverName, serverSecret, s2sUrl, false);
* S2S.authenticate(context, (ctx, result) => {
* GFV3.sysGetGlobalFileList(ctx, "", true, (ctx, result) => { ... });
* });
*
* File upload is a two-step process:
* 1. SYS_PREPARE_UPLOAD is sent via the S2S dispatcher and returns an uploadId + uploadUrl.
* 2. The file bytes are POSTed as multipart/form-data to the upload endpoint.
*/
// Default upload URL (fallback when context.url does not resolve to the upload endpoint)
var DEFAULT_UPLOAD_PATH = '/s2suploader/globalfile/upload';
// -----------------------------------------------------------------------
// File Info / Query
// -----------------------------------------------------------------------
/**
* Returns metadata for a global file identified by its fileId.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} fileId - Unique file identifier
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysGetFileInfo = (context, fileId, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_GET_FILE_INFO',
data: { fileId: fileId }
}, callback);
};
/**
* Returns metadata for a global file identified by folder path and filename.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} folderPath - Folder path (e.g. "myFolder/subFolder")
* @param {string} filename - File name
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysGetFileInfoSimple = (context, folderPath, filename, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_GET_FILE_INFO_SIMPLE',
data: { folderPath: folderPath, filename: filename }
}, callback);
};
/**
* Returns true if a file with the given name exists in the specified folder.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} folderPath - Folder path
* @param {string} filename - File name
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysCheckFilenameExists = (context, folderPath, filename, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_CHECK_FILENAME_EXISTS',
data: { folderPath: folderPath, filename: filename }
}, callback);
};
/**
* Returns true if a file exists at the given full path (e.g. "/folder/sub/file.txt").
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} fullpathFilename - Full path including filename
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysCheckFullpathFilenameExists = (context, fullpathFilename, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_CHECK_FULLPATH_FILENAME_EXISTS',
data: { fullPathFilename: fullpathFilename }
}, callback);
};
/**
* Returns the CDN URL for the global file identified by fileId.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} fileId - Unique file identifier
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysGetGlobalCDNUrl = (context, fileId, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_GET_GLOBAL_CDN_URL',
data: { fileId: fileId }
}, callback);
};
/**
* Lists all global files under the given folder path.
* Pass folderPath="" and recurse=true to list the entire tree.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} folderPath - Folder path; use "" for root
* @param {boolean} recurse - If true, list files in sub-folders as well
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysGetGlobalFileList = (context, folderPath, recurse, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_GET_GLOBAL_FILE_LIST',
data: { folderPath: folderPath, recurse: recurse }
}, callback);
};
// -----------------------------------------------------------------------
// File Management
// -----------------------------------------------------------------------
/**
* Moves a file from a user's personal cloud storage into the global file system.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} userProfileId - User profile ID
* @param {string} userCloudPath - Path in the user's cloud
* @param {string} userCloudFilename - Filename in the user's cloud
* @param {string} globalTreeId - Target folder tree ID
* @param {string} globalFilename - Filename in the global file system
* @param {boolean} overwriteIfPresent - Overwrite existing file if true
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysMoveToGlobalFile = (context, userProfileId, userCloudPath, userCloudFilename,
globalTreeId, globalFilename, overwriteIfPresent, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_MOVE_TO_GLOBAL_FILE',
data: {
userProfileId: userProfileId,
userCloudPath: userCloudPath,
userCloudFilename: userCloudFilename,
globalTreeId: globalTreeId,
globalFilename: globalFilename,
overwriteIfPresent: overwriteIfPresent
}
}, callback);
};
/**
* Copies a global file to another folder, optionally with a new name.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} fileId - File to copy
* @param {number} version - File version; pass -1 for latest
* @param {string} newTreeId - Destination folder tree ID
* @param {number} treeVersion - Destination tree version; pass -1 to skip check
* @param {string} newFilename - Filename in the destination folder
* @param {boolean} overwriteIfPresent - Overwrite existing file if true
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysCopyGlobalFile = (context, fileId, version, newTreeId, treeVersion,
newFilename, overwriteIfPresent, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_COPY_GLOBAL_FILE',
data: {
fileId: fileId,
version: version,
newTreeId: newTreeId,
treeVersion: treeVersion,
newFilename: newFilename,
overwriteIfPresent: overwriteIfPresent
}
}, callback);
};
/**
* Moves a global file to another folder, optionally with a new name.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} fileId - File to move
* @param {number} version - File version; pass -1 for latest
* @param {string} newTreeId - Destination folder tree ID
* @param {number} treeVersion - Destination tree version; pass -1 to skip check
* @param {string} newFilename - Filename in the destination folder
* @param {boolean} overwriteIfPresent - Overwrite existing file if true
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysMoveGlobalFile = (context, fileId, version, newTreeId, treeVersion,
newFilename, overwriteIfPresent, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_MOVE_GLOBAL_FILE',
data: {
fileId: fileId,
version: version,
newTreeId: newTreeId,
treeVersion: treeVersion,
newFilename: newFilename,
overwriteIfPresent: overwriteIfPresent
}
}, callback);
};
/**
* Deletes a single global file. Pass version=-1 to delete without a version check.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} fileId - File to delete
* @param {number} version - File version; pass -1 to skip check
* @param {string} filename - Filename
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysDeleteGlobalFile = (context, fileId, version, filename, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_DELETE_GLOBAL_FILE',
data: { fileId: fileId, version: version, filename: filename }
}, callback);
};
/**
* Deletes all global files in the specified folder.
* Set recurse=true to also delete files in sub-folders.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} treeId - Folder tree ID
* @param {string} folderPath - Folder path
* @param {number} treeVersion - Tree version; pass -1 to skip check
* @param {boolean} recurse - Delete files in sub-folders as well
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysDeleteGlobalFiles = (context, treeId, folderPath, treeVersion, recurse, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_DELETE_GLOBAL_FILES',
data: { treeId: treeId, folderPath: folderPath, treeVersion: treeVersion, recurse: recurse }
}, callback);
};
// -----------------------------------------------------------------------
// Folder Management
// -----------------------------------------------------------------------
/**
* Creates a new folder at the given path.
* Set createInterimDirectories=true to auto-create any missing parent folders.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} folderPath - Path for the new folder
* @param {number} treeVersion - Tree version; pass -1 to skip check
* @param {string} name - Folder name
* @param {string} desc - Folder description
* @param {boolean} createInterimDirectories - Create missing parent folders if true
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysCreateFolder = (context, folderPath, treeVersion, name, desc,
createInterimDirectories, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_CREATE_FOLDER',
data: {
folderPath: folderPath,
treeVersion: treeVersion,
name: name,
desc: desc,
createInterimDirectories: createInterimDirectories
}
}, callback);
};
/**
* Moves a folder to a new path, optionally renaming it.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} treeId - Folder tree ID
* @param {number} treeVersion - Tree version; pass -1 to skip check
* @param {string} newFolderPath - Destination path
* @param {string} updatedName - New folder name
* @param {boolean} createInterimDirectories - Create missing parent folders if true
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysMoveFolder = (context, treeId, treeVersion, newFolderPath, updatedName,
createInterimDirectories, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_MOVE_FOLDER',
data: {
treeId: treeId,
treeVersion: treeVersion,
newFolderPath: newFolderPath,
updatedName: updatedName,
createInterimDirectories: createInterimDirectories
}
}, callback);
};
/**
* Renames a folder in place.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} treeId - Folder tree ID
* @param {number} treeVersion - Tree version; pass -1 to skip check
* @param {string} updatedName - New folder name
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysRenameFolder = (context, treeId, treeVersion, updatedName, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_RENAME_FOLDER',
data: { treeId: treeId, treeVersion: treeVersion, updatedName: updatedName }
}, callback);
};
/**
* Resolves the treeId for a folder given its full path (e.g. "/folder/sub/").
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} fullFolderPath - Full folder path
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysLookupFolder = (context, fullFolderPath, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_LOOKUP_FOLDER',
data: { fullFolderPath: fullFolderPath }
}, callback);
};
/**
* Deletes a folder. Set force=true to also delete any files and sub-folders inside it.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} treeId - Folder tree ID
* @param {string} folderPath - Folder path
* @param {number} treeVersion - Tree version; pass -1 to skip check
* @param {boolean} force - Delete files and sub-folders inside the folder if true
* @param {function} callback - (context, result) where result is the parsed response
*/
exports.sysDeleteFolder = (context, treeId, folderPath, treeVersion, force, callback) => {
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_DELETE_FOLDER',
data: { treeId: treeId, folderPath: folderPath, treeVersion: treeVersion, force: force }
}, callback);
};
// -----------------------------------------------------------------------
// Upload
// -----------------------------------------------------------------------
/**
* Uploads a file to the brainCloud Global File V3 system via S2S.
*
* Internally performs SYS_PREPARE_UPLOAD to obtain an uploadId, then POSTs the
* file bytes to the upload endpoint as multipart/form-data. Metadata (gameId,
* uploadId) travels as URL query parameters; only the file bytes are in the body.
*
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} treeId - Folder tree ID (use "_root_" for root; call sysLookupFolder for sub-folders)
* @param {string} filename - Name of the file as it will appear in brainCloud
* @param {boolean} overwriteIfPresent - Replace any existing file with the same name
* @param {Buffer} fileData - File content as a Node.js Buffer (or any Uint8Array)
* @param {function} callback - (context, result) where result is the parsed upload response
*/
exports.uploadGlobalFile = (context, treeId, filename, overwriteIfPresent, fileData, callback) => {
if (context.logEnabled) {
console.log('[GlobalFileV3] Preparing upload: ' + filename +
' (' + fileData.length + ' bytes) treeId=' + treeId);
}
brainclouds2s.request(context, {
service: 'globalFileV3',
operation: 'SYS_PREPARE_UPLOAD',
data: {
treeId: treeId,
filename: filename,
overwriteIfPresent: overwriteIfPresent,
fileSize: fileData.length
}
}, (ctx, result) => {
if (!result || result.status !== 200) {
if (context.logEnabled) {
console.log('[GlobalFileV3] SYS_PREPARE_UPLOAD failed: ' + JSON.stringify(result));
}
if (callback) callback(ctx, result);
return;
}
var fileDetails = result.data && result.data.fileDetails;
if (!fileDetails || !fileDetails.uploadId) {
if (context.logEnabled) {
console.log('[GlobalFileV3] SYS_PREPARE_UPLOAD missing fileDetails/uploadId: ' +
JSON.stringify(result));
}
if (callback) callback(ctx, result);
return;
}
var uploadId = fileDetails.uploadId;
var uploadUrl = buildUploadUrl(context, fileDetails, uploadId);
if (context.logEnabled) {
console.log('[GlobalFileV3] Uploading to: ' + uploadUrl);
}
sendFileUpload(context, uploadUrl, filename, fileData, callback);
});
};
// -----------------------------------------------------------------------
// Internal helpers
// -----------------------------------------------------------------------
/**
* Constructs an absolute upload URL from the prepare response.
* If the server returned a relative uploadUrl we prefix it with the scheme and host.
* Falls back to deriving the URL from context.url.
*/
function buildUploadUrl(context, fileDetails, uploadId) {
if (fileDetails.uploadUrl) {
var relativeUrl = fileDetails.uploadUrl;
if (relativeUrl.startsWith('http')) {
return relativeUrl;
}
// Relative path returned by server — prefix with scheme + host
return 'https://' + context.url + relativeUrl;
}
// Fallback: construct from context.url (hostname only)
return 'https://' + context.url + DEFAULT_UPLOAD_PATH +
'?gameId=' + encodeURIComponent(context.appId) +
'&uploadId=' + encodeURIComponent(uploadId);
}
/**
* POSTs file bytes to uploadUrl as multipart/form-data using the Node.js https module.
* All metadata is carried as URL query parameters; only the file bytes travel in the body.
*/
function sendFileUpload(context, uploadUrl, filename, fileData, callback) {
var boundary = '----BrainCloudS2SBoundary' + Date.now();
var fileBuffer = Buffer.isBuffer(fileData) ? fileData : Buffer.from(fileData);
var headerPart = Buffer.from(
'--' + boundary + '\r\n' +
'Content-Disposition: form-data; name="file"; filename="' + filename + '"\r\n' +
'Content-Type: application/octet-stream\r\n\r\n'
);
var footerPart = Buffer.from('\r\n--' + boundary + '--\r\n');
var body = Buffer.concat([headerPart, fileBuffer, footerPart]);
var parsedUrl = new URL(uploadUrl);
var options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || 443,
path: parsedUrl.pathname + parsedUrl.search,
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': body.length
}
};
if (context.logEnabled) {
console.log('[GlobalFileV3] POST ' + options.hostname + options.path +
' (' + body.length + ' bytes)');
}
var req = https.request(options, (res) => {
var data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
if (context.logEnabled) {
console.log('[GlobalFileV3] Upload response: ' + data);
}
var responseData = null;
try { responseData = JSON.parse(data); } catch (e) {}
if (callback) callback(context, responseData);
});
});
req.on('error', (err) => {
if (context.logEnabled) {
console.log('[GlobalFileV3] Upload error: ' + err.message);
}
if (callback) callback(context, { status: 900, status_message: 'File upload failed: ' + err.message });
});
req.write(body);
req.end();
}
'use strict';
//----------------------------------------------------
// brainCloud client source code
// Copyright 2026 bitHeads, inc.
//----------------------------------------------------
var brainclouds2s = require('./brainclouds2s');
/**
* Handles the Pre-Ready Launch (PRL) flow for custom servers launched by brainCloud.
*
* When PRE_READY_LAUNCH is "true", the server must wait for the assigned lobby to
* reach the "starting" state before proceeding with launch.
*
* Usage:
* const prl = require('./brainclouds2s-prl');
* if (prl.isPreReadyLaunch()) {
* brainclouds2s.authenticate(context, (ctx, result) => {
* prl.start(context, lobbyId, (proceed) => {
* if (proceed) { ... } else { process.exit(0); }
* });
* });
* }
*/
/**
* Returns true if PRE_READY_LAUNCH environment variable is set to "true".
*/
exports.isPreReadyLaunch = () => {
var val = process.env['PRE_READY_LAUNCH'];
return val != null && val.toLowerCase() === 'true';
};
/**
* Returns the timeout in seconds from PRL_TIMEOUT_SECS or
* PRE_READY_LAUNCH_TIMEOUT_SECS environment variables. Defaults to 60.
*/
exports.getTimeoutSecs = () => {
var val = process.env['PRL_TIMEOUT_SECS'];
if (val) { var s = parseInt(val); if (!isNaN(s)) return s; }
val = process.env['PRE_READY_LAUNCH_TIMEOUT_SECS'];
if (val) { var s = parseInt(val); if (!isNaN(s)) return s; }
return 60;
};
/**
* Parses the SERVER_CONTEXT environment variable into an object.
* Handles single-quoted and backslash-escaped JSON strings.
*/
exports.parseServerContext = () => {
try {
var val = process.env['SERVER_CONTEXT'] || '{}';
val = val.trim().replace(/^'|'$/g, '').replace(/\\"/g, '"');
return JSON.parse(val);
} catch (e) {
return {};
}
};
/**
* Builds the lobby RTT channel ID from the app ID and lobby ID.
* Lobby ID format: <appId>:<instanceId>
* Channel format: <appId>:sy:_lobby_<instanceId>
*/
function buildChannelId(appId, lobbyId) {
var instanceId = lobbyId;
var colonPos = lobbyId.indexOf(':');
if (colonPos >= 0) instanceId = lobbyId.substring(colonPos + 1);
return appId + ':sy:_lobby_' + instanceId;
}
/**
* Parses the lobby state from a GET_LOBBY_DATA response.
* @param {object} result - S2S response object
* @returns {string|null} lobby state string, or null on failure
*/
function parseLobbyState(result) {
try {
if (!result || result.status !== 200) return null;
return result.data.state || null;
} catch (e) { return null; }
}
/**
* Parses the lobby state from an RTT push message.
* Expected format: { service: "chat", operation: "INCOMING", data: { content: { data: { lobby: { state: "..." } } } } }
* @param {object} msg - RTT message object
* @returns {string|null} lobby state string, or null if not a lobby state message
*/
function parseLobbyStateFromRTT(msg) {
try {
if (msg.service !== 'chat') return null;
if (msg.operation !== 'INCOMING') return null;
return msg.data.content.data.lobby.state || null;
} catch (e) { return null; }
}
/**
* Sends SYS_ROOM_SESSION_ENDED to notify brainCloud the server session has concluded.
* Should be called before the server process exits.
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {function} [callback] - Optional callback with signature (context, result)
*/
exports.sendSessionEnded = (context, callback) => {
console.log('[S2S PRL] Sending SYS_ROOM_SESSION_ENDED.');
brainclouds2s.request(context, {
service: 'roomServer',
operation: 'SYS_ROOM_SESSION_ENDED',
data: {
serverId: process.env['SERVER_ID'],
serverContext: exports.parseServerContext()
}
}, callback || null);
};
/**
* Starts the PRL flow. Assumes the S2S context is already authenticated.
*
* Flow:
* 1. Enable RTT
* 2. Subscribe to the lobby status channel (chat/CHANNEL_CONNECT)
* 3. Notify brainCloud the room session has started (roomServer/SYS_ROOM_SESSION_STARTED)
* 4. Query the lobby state (lobby/GET_LOBBY_DATA)
* 5. If state is "starting" → proceed. If "disbanded" or not found → exit.
* Otherwise wait for an RTT push with the state transition.
*
* @param {object} context - S2S context returned by brainclouds2s.init()
* @param {string} lobbyId - The lobby ID
* @param {function} callback - Called with (proceed: boolean) when PRL is complete
*/
exports.start = (context, lobbyId, callback) => {
var timeoutSecs = exports.getTimeoutSecs();
var complete = false;
var timeoutId = null;
console.log('[S2S PRL] Starting PRL flow. lobbyId=' + lobbyId + ', timeoutSecs=' + timeoutSecs);
function done(proceed) {
if (complete) return;
complete = true;
if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; }
brainclouds2s.deregisterRTTRawCallback();
callback(proceed);
}
if (timeoutSecs > 0) {
timeoutId = setTimeout(() => {
console.log('[S2S PRL] Timeout elapsed — exiting.');
done(false);
}, timeoutSecs * 1000);
}
function handleLobbyState(lobbyState) {
if (lobbyState === null || lobbyState === undefined) {
console.log('[S2S PRL] Lobby not found — exiting.');
done(false);
} else if (lobbyState === 'disbanded') {
console.log('[S2S PRL] Lobby disbanded — exiting.');
done(false);
} else if (lobbyState === 'starting') {
console.log('[S2S PRL] Lobby is starting — proceeding with launch.');
done(true);
} else {
console.log('[S2S PRL] Lobby state is "' + lobbyState + '" — waiting for RTT update.');
}
}
// Register RTT callback to receive lobby state push notifications
brainclouds2s.registerRTTRawCallback((msg) => {
console.log('[S2S PRL] RTT message: ' + JSON.stringify(msg));
if (complete) return;
var lobbyState = parseLobbyStateFromRTT(msg);
if (lobbyState !== null) {
console.log('[S2S PRL] RTT lobby state update: ' + lobbyState);
handleLobbyState(lobbyState);
}
});
// Step 1: Enable RTT
brainclouds2s.enableRTT(context,
(rttResult) => {
var channelId = buildChannelId(context.appId, lobbyId);
console.log('[S2S PRL] RTT connected. Subscribing to channel: ' + channelId);
// Step 2: Subscribe to the lobby status channel
brainclouds2s.request(context, {
service: 'chat',
operation: 'CHANNEL_CONNECT',
data: {
channelId: channelId,
maxReturn: 50
}
}, (ctx, result) => {
if (!result || result.status !== 200) {
console.log('[S2S PRL] Failed to subscribe to channel: ' + JSON.stringify(result));
done(false);
return;
}
console.log('[S2S PRL] Channel subscribed. Notifying session started.');
// Step 3: Notify brainCloud the room session has started
var serverContext = exports.parseServerContext();
console.log('[S2S PRL] SERVER_CONTEXT = ' + JSON.stringify(serverContext));
brainclouds2s.request(context, {
service: 'roomServer',
operation: 'SYS_ROOM_SESSION_STARTED',
data: {
serverId: process.env['SERVER_ID'],
serverContext: serverContext
}
}, (ctx, result) => {
if (!result || result.status !== 200) {
console.log('[S2S PRL] SYS_ROOM_SESSION_STARTED failed: ' + JSON.stringify(result));
done(false);
return;
}
console.log('[S2S PRL] Session started. Querying lobby state.');
// Step 4: Query the current lobby state
brainclouds2s.request(context, {
service: 'lobby',
operation: 'GET_LOBBY_DATA',
data: { lobbyId: lobbyId }
}, (ctx, result) => {
var lobbyState = parseLobbyState(result);
console.log('[S2S PRL] Initial lobby state: ' + lobbyState);
handleLobbyState(lobbyState);
});
});
});
},
(err) => {
console.log('[S2S PRL] RTT connection failed: ' + JSON.stringify(err));
done(false);
}
);
};
+54
-36

@@ -0,3 +1,10 @@

'use strict';
let S2S = require('./brainclouds2s.js');
// Use the native WebSocket if available (Node.js 22+), otherwise fall back to ws
if (typeof WebSocket === 'undefined') {
var WebSocket = require('ws');
}
var socket = null

@@ -166,6 +173,13 @@

if (typeof e.data === "string") {
processResult(e.data);
// String data (Node.js ws library or modern browsers) — parse as JSON
var parsed = {};
try {
parsed = JSON.parse(e.data);
} catch (err) {
console.log("WS RECV parse error: " + err + " data=" + e.data);
return;
}
processResult(parsed);
} else if (typeof FileReader !== 'undefined') {
// Web Browser
// Web Browser — binary/blob data
var reader = new FileReader();

@@ -176,7 +190,6 @@ reader.onload = function () {

parsed = JSON.parse(reader.result);
} catch (err) {
console.log("WS RECV parse error: " + err + " data=" + reader.result);
return;
}
catch (e) {
console.log("WS RECV: " + reader.result);
parsed = JSON.parse(reader.result); // Trigger the error again and let it fail
}
processResult(parsed);

@@ -186,12 +199,10 @@ }

} else {
// Node.js
// Fallback
var parsed = {};
try {
parsed = JSON.parse(e.data);
} catch (err) {
console.log("WS RECV parse error: " + err + " data=" + e.data);
return;
}
catch (e) {
console.log("WS RECV: " + e.data);
parsed = JSON.parse(e.data); // Trigger the error again and let it fail
}
processResult(parsed);

@@ -218,34 +229,41 @@ }

* Gets the name of the browser being used.
* @returns name of browser
* Returns null in non-browser environments (e.g. Node.js).
* @returns name of browser, or null
*/
function getBrowserName() {
// Opera 8.0+
var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || (typeof navigator !== 'undefined' && navigator.userAgent.indexOf(' OPR/') >= 0);
if (typeof window === 'undefined') {
return null; // Node.js environment
}
try {
// Opera 8.0+
var isOpera = (!!window.opr && !!window.opr.addons) || !!window.opera || (typeof navigator !== 'undefined' && navigator.userAgent.indexOf(' OPR/') >= 0);
// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';
// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';
// Safari 3.0+ "[object HTMLElementConstructor]"
var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
// Safari 3.0+
var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
// Internet Explorer 6-11
var isIE = (typeof document !== 'undefined' && !!document.documentMode);
// Internet Explorer 6-11
var isIE = (typeof document !== 'undefined' && !!document.documentMode);
// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;
// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;
// Chrome 1+
var isChrome = !!window.chrome && !!window.chrome.webstore;
// Chrome 1+
var isChrome = !!window.chrome && !!window.chrome.webstore;
// Blink engine detection
var isBlink = (isChrome || isOpera) && !!window.CSS;
// Blink engine detection
var isBlink = (isChrome || isOpera) && !!window.CSS;
if (isOpera) return "opera";
if (isFirefox) return "firefox";
if (isSafari) return "safari";
if (isIE) return "ie";
if (isEdge) return "edge";
if (isChrome) return "chrome";
if (isBlink) return "blink";
if (isOpera) return "opera";
if (isFirefox) return "firefox";
if (isSafari) return "safari";
if (isIE) return "ie";
if (isEdge) return "edge";
if (isChrome) return "chrome";
if (isBlink) return "blink";
} catch (e) {
// ignore
}
return null;

@@ -252,0 +270,0 @@ }

+1
-1
{
"name": "brainclouds2s",
"version": "5.9.0",
"version": "6.0.0",
"description": "brianCloud S2S module",

@@ -5,0 +5,0 @@ "main": "brainclouds2s.js",

+243
-0
const fs = require('fs')
let S2S = require('./brainclouds2s.js');
let GFV3 = require('./brainclouds2s-globalfilev3.js');

@@ -527,5 +528,247 @@ /**

async function run_globalfilev3_tests()
{
if (!module("GlobalFileV3", null, null)) return;
// Shared state: captured from SysCreateFolder and UploadGlobalFile responses
var gfv3FolderTreeId = "";
var gfv3FileId = "";
var gfv3FileVersion = 1;
// Test 1 (parity: dotnet #8): SysGetGlobalFileList
await asyncTest("sysGetGlobalFileList", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysGetGlobalFileList(s2s, "", true, (s2s, result) =>
{
equal(result && result.status, 200, "SysGetGlobalFileList: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 2 (parity: dotnet #9): SysLookupFolder
await asyncTest("sysLookupFolder", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysLookupFolder(s2s, "s2s_test_folder", (s2s, result) =>
{
equal(result && result.status, 200, "SysLookupFolder: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 3 (parity: dotnet #10): SysCreateFolder — captures treeId
await asyncTest("sysCreateFolder", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysCreateFolder(s2s, "s2s_test_folder", -1, "s2s_test_folder_2",
"S2S integration test folder", false, (s2s, result) =>
{
if (result && result.status === 200 && result.data && result.data.createdTreeId) {
gfv3FolderTreeId = result.data.createdTreeId;
}
equal(result && result.status, 200, "SysCreateFolder: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 4 (parity: dotnet #11): UploadGlobalFile — captures fileId and version
await asyncTest("uploadGlobalFile", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.setLogEnabled(s2s, true);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
var fileData = Buffer.from("Hello from brainCloud S2S file upload test!");
GFV3.uploadGlobalFile(s2s, gfv3FolderTreeId, "s2s_test_file.txt", true, fileData,
(s2s, result) =>
{
if (result && result.status === 200 &&
result.data && result.data.fileDetails && result.data.fileDetails.fileDetails) {
var fd = result.data.fileDetails.fileDetails;
gfv3FileId = fd.fileId || gfv3FileId;
gfv3FileVersion = fd.version || gfv3FileVersion;
}
equal(result && result.status, 200, "UploadGlobalFile: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 5 (parity: dotnet #12): SysGetFileInfo
await asyncTest("sysGetFileInfo", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysGetFileInfo(s2s, gfv3FileId, (s2s, result) =>
{
equal(result && result.status, 200, "SysGetFileInfo: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 6 (parity: dotnet #13): SysGetFileInfoSimple
await asyncTest("sysGetFileInfoSimple", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysGetFileInfoSimple(s2s, "s2s_test_folder/s2s_test_folder_2", "s2s_test_file.txt",
(s2s, result) =>
{
equal(result && result.status, 200, "SysGetFileInfoSimple: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 7 (parity: dotnet #14): SysCheckFilenameExists
await asyncTest("sysCheckFilenameExists", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysCheckFilenameExists(s2s, "s2s_test_folder/s2s_test_folder_2", "s2s_test_file.txt",
(s2s, result) =>
{
equal(result && result.status, 200, "SysCheckFilenameExists: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 8 (parity: dotnet #15): SysCheckFullpathFilenameExists
await asyncTest("sysCheckFullpathFilenameExists", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysCheckFullpathFilenameExists(s2s,
"s2s_test_folder/s2s_test_folder_2/s2s_test_file.txt", (s2s, result) =>
{
equal(result && result.status, 200,
"SysCheckFullpathFilenameExists: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 9 (parity: dotnet #16): SysGetGlobalCDNUrl
await asyncTest("sysGetGlobalCDNUrl", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysGetGlobalCDNUrl(s2s, gfv3FileId, (s2s, result) =>
{
equal(result && result.status, 200, "SysGetGlobalCDNUrl: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 10 (parity: dotnet #17): SysCopyGlobalFile
await asyncTest("sysCopyGlobalFile", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysCopyGlobalFile(s2s, gfv3FileId, gfv3FileVersion, gfv3FolderTreeId, -1,
"s2s_file_copy.txt", true, (s2s, result) =>
{
equal(result && result.status, 200, "SysCopyGlobalFile: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 11 (parity: dotnet #18): SysMoveGlobalFile
await asyncTest("sysMoveGlobalFile", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysMoveGlobalFile(s2s, gfv3FileId, gfv3FileVersion, gfv3FolderTreeId, -1,
"s2s_file_moved.txt", true, (s2s, result) =>
{
equal(result && result.status, 200, "SysMoveGlobalFile: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 12 (parity: dotnet #19): SysRenameFolder
await asyncTest("sysRenameFolder", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysRenameFolder(s2s, gfv3FolderTreeId, -1, "s2s_test_folder_renamed", (s2s, result) =>
{
equal(result && result.status, 200, "SysRenameFolder: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 13 (parity: dotnet #20): SysDeleteGlobalFiles — cleanup files
await asyncTest("sysDeleteGlobalFiles", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysDeleteGlobalFiles(s2s, gfv3FolderTreeId,
"s2s_test_folder/s2s_test_folder_renamed", -1, true, (s2s, result) =>
{
equal(result && result.status, 200, "SysDeleteGlobalFiles: " + JSON.stringify(result));
resolve_test();
});
});
});
// Test 14 (parity: dotnet #21): SysDeleteFolder — cleanup folder
await asyncTest("sysDeleteFolder", 2, () =>
{
let s2s = S2S.init(GAME_ID, SERVER_NAME, SERVER_SECRET, S2S_URL, false);
S2S.authenticate(s2s, (s2s, result) =>
{
equal(result && result.status, 200, "Auth: " + JSON.stringify(result));
GFV3.sysDeleteFolder(s2s, gfv3FolderTreeId,
"s2s_test_folder/s2s_test_folder_renamed", -1, true, (s2s, result) =>
{
equal(result && result.status, 200, "SysDeleteFolder: " + JSON.stringify(result));
resolve_test();
});
});
});
}
async function main()
{
await run_tests();
await run_globalfilev3_tests();

@@ -532,0 +775,0 @@ console.log(((test_passed === test_count) ? "\x1b[32m[PASSED] " : "\x1b[31m[FAILED] ") + test_passed + "/" + test_count + " passed\x1b[0m");