• Examples (iOS)
• Examples (Android)
• Examples (C++)
Server API Documentation

 

» Custom Admin Modules

Starting with SmartFoxServer 2X v2.16 we introduced the possibility to create custom modules for our AdminTool. When ready, a module can be deployed in the AdminTool folder of your SFS2X instance to provide integrated configuration, monitoring and management capabilities of your game or application.

» Introduction

In multiplayer games and applications, a common requirement is to be able to monitor how users are behaving during their sessions. In general, this is something the default AdminTool's modules (in particular Zone Monitor and Analytics) provide at a global level, based on the server status being monitored in realtime (or not).
But oftentimes developers also need to manage their application "on-the-fly", or monitor its status through specific parameters which are inner to the server side Extension and to which the default modules don't have access (and wouldn't know how to treat anyway).

A few use cases:

Custom AdminTool Modules have been introduced to help developers easily create their own monitoring and management tools, specific for their games and applications, and collect them in one convenient place. The AdminTool provides the connection to the server and a secure admin login system, it offers an existing interface with useful basic features (for example notifications and alerts) and it integrates well known UI frameworks (like Bootstrap), so developers can concentrate on the core logic of their tools and speed up the development process.

An AdminTool module is made of two separate parts: a server side request handler and the client side user interface. In the following chapters we will describe how to create a sort of template module, showing how to develop both parts and handle the network communication between the two. Files can be downloaded at the bottom of this tutorial.

We will call our custom module Game Manager.

And for the purpose of this tutorial, we will also assume that a MyGame Zone exists in SmartFoxServer, with its own Java Extension attached to it. The Extension handles the server side logic of our hypothetical game.

» Requirements

We need a Java IDE to assist us in writing the server side code, compiling it and creating a binary package (jar file) that will be deployed to SFS2X. In this tutorial we will be using Eclipse.

A text editor and an image editor capable of exporting in svg format is then needed for the module definition. The text editor is enough for the client side coding (both HTML5 and JavaScript) too, but if you want to use an IDE, we suggest Atom.

Finally, some experience in SFS2X Extensions development is required, as you will better understand most of the concepts discussed here.

» Module development pt.1: definition

In this chapter we will show how to define a new module, so that it is loaded by SmartFoxServer when started.

» Module definition

Defining the custom module is the first step we need to take. In order to do it, let's browse the SFS2X installation folder looking for this file: {sfs-install-dir}/SFS2X/config/admin/admintool.xml. Open it with a text editor.

This file contains the definition of all the default AdminTool modules, and this is where we will add our custom one too. A custom module definition needs the following attributes.

Let's add the following line to the modules' definition file. The position in the list will determine where the module's icon will appear in the AdminTool interface. In our example it will be the last one.

<module id="GameManager" name="Game Manager" description="Management tool for our game" className="my.namespace.GameManagerReqHandler"/>

» Module icon

The module also requires a vector icon in SVG format. For coherence, we suggest to use a white + orange (#fd7d25) color scheme. The following is an example of icon on a dark background.

The icon must be saved under the {sfs-install-dir}/SFS2X/config/admin/icons folder. Depending on the SFS2X version, in the same folder you may also find icons in PNG format. Those are legacy icons still available for compatibility with an older version of the AdminTool. Ignore them.

» Module development pt.2: the server side

In this chapter we will show how to create a basic request handler listening to commands coming from the module's client, and how to deploy it.

» Project setup

A request handler serves the purpose of receiving a specific set of requests from the client and sending back the related responses. The "request handler" is one of the core concepts of SmartFoxServer Extensions development, so more information is available here.

Specifically, the request handler of a custom AdminTool module receives commands from the module's client.

Let's start Eclipse and from the File menu choose New > Java Project. In the window that pos up, we give the project a name, for example GameManagerCustomModule, and proceed to the next screen.
Now it is time to add the libraries that are needed to compile our handler. Click on Libraries tab, then on the Add External JARs... button and browse your file system to your {sfs-install-dir}/SFS2X/lib folder.
Select three files: sfs2x-admin.jar, sfs2x-core.jar and sfs2x.jar, then click the Finish button in the window.

The new project will appear in the Package Explorer panel which by default is located at the top left column of your interface. Now you can create a new Java class by right-clicking on the src folder and selecting New > Class from the menu.

Here we can enter the name of the class, GameManagerReqHandler (as declared in the module definition), and its package, my.namespace (again as declared before). After clicking on the Finish button, the newly created class will look like this:

package my.namespace;

public class GameManagerReqHandler
{

}

» Base handler code

We can now add the boilerplate code to turn the newly created class into the request handler that we need.

package my.namespace;

@MultiHandler
@Instantiation(InstantiationMode.SINGLE_INSTANCE)
public class GameManagerReqHandler extends BaseAdminModuleReqHandler
{
	public static final String MODULE_ID = "GameManager";
	private static final String COMMANDS_PREFIX = "gameMan";

	public CustomToolReqHandler()
	{
		super(COMMANDS_PREFIX, MODULE_ID);
	}

	@Override
	protected void handleAdminRequest(User sender, ISFSObject params)
	{
		String cmd = params.getUtfString(SFSExtension.MULTIHANDLER_REQUEST_ID);
	}
}

We added the following elements:

The handleAdminRequest method is the core of the module's server side handler. This is where all commands coming from the client are processed. With the help of an if-else statement we can execute different actions based on the actual command sent by the client.

As an example, let's suppose we want to know the total number of users connected to our MyGame Zone (remember we assumed in the beginning that this Zone exists on the server and it has an Extension attached). This is requested by the command "userCount"; an additional parameter is also sent by the client to indicate if we want to retrieve the total number of users or just the actual players (assuming that our game allows users to join as spectators too). Let's update the handleAdminRequest method:

@Override
protected void handleAdminRequest(User sender, ISFSObject params)
{
	String cmd = params.getUtfString(SFSExtension.MULTIHANDLER_REQUEST_ID);
	
	if (cmd.equals("userCount"))
	{
		// Retrieve flag sent by the client
		boolean playersOnly = params.getBool("playersOnly");
		
		// Retrieve Zone
		Zone myZone = sfs.getZoneManager().getZoneByName("MyGame");
		
		// Count users
		int count = 0;
		
		if (!playersOnly)
			count = myZone.getUserCount();
		else
		{
			for (Room room : myZone.getRoomList()) {
				count += room.getPlayersList().size();
			}
		}
		
		// Send response back to client
		ISFSObject outParams = new SFSObject();
		outParams.putInt("value", count);
		outParams.putBool("playersOnly", playersOnly);
		
		sendResponse("userCount", outParams, sender);
	}
}

In this code, the main if checks what command (request) was sent by the client, which is then processed.

First of all, we know that this request comes with an additional boolean parameter, which we retrive; then the appropriate count is made. Finally the response is sent back to the sender of the request (aka the client part of our module) using a service method made available by the BaseAdminModuleReqHandler parent class. The first parameter passed to the sendResponse method is the string identifier of the response, which the client will use to know what to do with the returned data. For convenience we use the same identifier of the request ("userCount"), but this is not mandatory. Among the returned data we also send the flag indicating if the user count includes all users or players only.

» Communicating with the Extension

As mentioned in the introduction, when creating a custom module the actual benefit comes from being able to access our game Extension to extract informations on the game state, send commands and more.

Communicating with an Extension from the request handler can be easily achieved through the Extension's handleInternalMessage method, described in the Java Extensions in-depth overview (see Extension interoperability paragraph).

For example we want to display some overall stats about "spells" cast by players in the game, for example the total amount per spell type. Let's assume this information is requested by the module's client through a generic "stats" command (which may include other stats too).

@Override
protected void handleAdminRequest(User sender, ISFSObject params)
{
	String cmd = params.getUtfString(SFSExtension.MULTIHANDLER_REQUEST_ID);
	
	if (cmd.equals("userCount"))
	{
		// ...
	}
	
	else if (cmd.equals("stats"))
	{
		// Get a reference to the Zone Extension
		ISFSExtension ext = sfs.getZoneManager().getZoneByName("MyGame").getExtension();
		
		// Extract stats about "spells" from Extension
		ISFSObject spellsObj = (ISFSObject) ext.handleInternalMessage("spells", null);
		
		// Send response back to client
		ISFSObject outParams = new SFSObject();
		outParams.putSFSObject("spells", spellsObj);
		//outParams.putSFSObject("others", otherObj); // Other stats collected by the Extension
		
		sendResponse("stats", outParams, sender);
	}
}

Adding an else to the main if, we identify the new request coming from the client. There we get a reference to the Extension attached to our MyGame Zone and get the data we need using the handleInternalMessage method, passing an identifier of the data we need to extract ("spells"). The second parameter of the method is set to null, because in this example we don't need to pass additional parameters (which could be useful in a real case scenario, for example to provide a filter). For convenience, the call to the Extension returns an SFSObject, which we can immediately send back to the client. Again, the response uses the same identifier of the request ("stats").

For the sake of completeness, the following code shows how the handleInternalMessage method could look like inside the Extension code:

@Override
public Object handleInternalMessage(String cmdName, Object params)
{
	if (cmdName.equals("stats"))
	{
		ISFSObject spellsObj = new SFSObject();
		spellsObj.putInt("teleport", this.getTeleportSpellCount());
		spellsObj.putInt("fireball", this.getFireballSpellCount());
		spellsObj.putInt("protection", this.getProtectionSpellCount());
		spellsObj.putInt("heal", this.getHealSpellCount());
		
		return spellsObj;
	}
	
	return null;
}

» Deployment

Assuming our handler is now complete, we can deploy it in SmartFoxServer. In the Package Explorer right-click the project folder and choose Export.... In the next dialogue box open the Java folder and choose JAR file, then click the Next button. In the JAR Export window click the Browse... button and navigate to the {sfs-install-dir}/SFS2X/extensions/__lib__, specifying then a name for the jar file to be created (for example GameManagerAdminModule).

We can now start (or restart) SmartFoxServer. Just have a look at the startup log, to make sure the request handler is loaded without errors.

» Module development pt.3: the client side

In this chapter we will show how to develop and deploy the client part of a custom AdminTool module.

The AdminTool application is based on the Custom Elements web standard. This means that our custom AdminTool module must follow the same approach: in a nutshell, we have to declare a custom html tag and the JavaScript class defining it — in other words, the view and its controller.

The purpose of the following paragraphs is to show how to create a very simple module displaying the data returned in response to the "userCount" and "stats" requests handled by the server side handler described before.

» Creating the HTML5 view

The module's html is made of a single custom tag at the root level, which then encapsulates all the other html elements which make up our interface. The name of the custom tag must follow a strict naming convention: its the module ID in lowercase characters, with all the words separated by a dash (kebab-case) and followed by -module. The tag must also have the CSS class module applied to it.

We can now open our editor of choice and type:

<game-manager-module class="module"></game-manager-module>

We can now add more elements to our view, including some styling. In order to prevent possible conflicts in the DOM, we strongly recommend that you use a prefix for all IDs and class names. Alternatively it is possible to make use of the Custom Element's Shadow DOM, although this is not mandatory because we are not creating an actually reusable web component (for which the Shadow DOM comes in handy).

In our example we go with a plain DOM and use the "gm-" prefix where needed:

<style>
	game-manager-module {
		padding: 1rem;
	}

	.gm-output {
		padding: 1rem;
		margin-top: 1rem;
		background-color: #ddd;
		border-radius: .5rem;
	}
</style>

<game-manager-module class="module">
	<div>
		<button id="gm-usersBt" type="button" class="gm-button">Get users count</button>
		<button id="gm-playersBt" type="button" class="gm-button">Get players count</button>
		<button id="gm-spellsBt" type="button" class="gm-button">Get spells stats</button>
	</div>
	<div id="gm-outputArea" class="gm-output">Click on a button.</div>
</game-manager-module>

Other than the <style> tag, our view contains 3 buttons and a <div> in which we will display the data returned by our server side request handler.

You will later find out that some html elements, like the buttons, come with a styling different from the browser default. This is due to the frameworks we use in the AdminTool. More on this subject at the end of the chapter.

Save the html file with the name game-manager.html (module ID in lowercase and kebab-case).

» The JavaScript controller behind the view

It is now time write the actual logic of our module. Let's start with the basic scaffolding:

export default class GameManager extends BaseModule
{
	constructor()
	{
	    super('gameMan');
	}

	initialize(idData, shellController)
	{
		// Call super method
		super.initialize(idData, shellController);
	}

	destroy()
	{
		// Call super method
		super.destroy();
	}

	onExtensionCommand(cmd, data)
	{
		
	}
}

Please note the following:

NOTE
The AdminTool will take care of loading this class dynamically and use it to "upgrade" the custom html element we defined in our module's view. We don't have to explicitly call the window.customElements.define method as normal in Custom Elements development.

We can now write the code to send requests to and receive responses from the server side request handler.

export default class GameManager extends BaseModule
{
	constructor()
	{
	    super('gameMan');
	}

	initialize(idData, shellController)
	{
		// Call super method
		super.initialize(idData, shellController);

		// Add buttons click listeners
		document.getElementById('gm-usersBt').addEventListener('click', () => this._onUserCountReqClick(false));
		document.getElementById('gm-playersBt').addEventListener('click', () => this._onUserCountReqClick(true));
		document.getElementById('gm-statsBt').addEventListener('click', () => this._onStatsReqClick());
	}

	destroy()
	{
		// Call super method
		super.destroy();
	}

	onExtensionCommand(cmd, data)
	{
		// Clear output area
		document.getElementById('gm-outputArea').innerHTML = '';

		// Handle response to "userCount" request
		if (cmd == 'userCount')
		{
			const playersOnly = data.getBool('playersOnly');
			const count = data.getInt('value');

			document.getElementById('gm-outputArea').innerHTML = `Total ${playersOnly ? 'players' : 'users'}: ${count}`;
		}

		// Handle response to "stats" request
		else if (cmd == 'stats')
		{
			const spellStats = data.getSFSObject('spells');
			const spells = spellStats.getKeysArray();

			for (let spell of spells)
				document.getElementById('gm-outputArea').innerHTML += `${spell}: ${spellStats.getInt(spell)}
`; } } _onUserCountReqClick(playersOnly) { const params = new SFS2X.SFSObject(); params.putBool('playersOnly', playersOnly); this.sendExtensionRequest('userCount', params); } _onStatsReqClick() { this.sendExtensionRequest('stats'); } }

This is what we did:

Save the js file with the name game-manager.js (module ID in lowercase and kebab-case).

» Deployment

We are now ready to deploy the client part of our custom module. First of all we need to browse the folder containing the AdminTool in our SmartFoxServer2X instance: {sfs-install-dir}/SFS2X/www/ROOT/admin.

Copy the html file (game-manager.html) under the /modules subfolder.

Copy the js file (game-manager.js) under the /assets/js/custom-modules subfolder.

We can now finally launch the AdminTool (local url: http://localhost:8080/admin), click on the newly added module to open it and click on one of the buttons in the UI to test the data retrieval:

» A note on frameworks

The AdminTool makes use of the following frameworks and libraries, which are then available to you in the development of your custom modules:

The following library is also included in the AdminTool, but you are not allowed to use it unless you own or acquire a license.

» Download

The module definition and icon, the server side request handler Java project and the client side HTML and JavaScript files can be downloaded as a zip file with the following link:

» Appendix

As discussed above, when developing a custom module, you need to extend a base class both on the server side (BaseAdminModuleReqHandler) and on the client side (BaseModule). This appendix provides a list of the methods an properties you may need to access to.

» Server side

BaseAdminModuleReqHandler class

Type Method
void
handleAdminRequest(User sender, ISFSObject params)

Abstract method to be implemented by the custom request handler. This method receives the requests from the client side of the custom AdminTool module.

Parameters

sender [User]: the User who sent the request.

params [SFSObject]: an object containing the parameters sent by the client.

void
sendResponse(String cmd, User recipient)

Sends a simple command to a specific user, without additional parameters.

Parameters

cmd [String]: the identifier of the response.

recipient [User]: the User to send the response to.

void
sendResponse(String cmd, List<User> recipients)

Sends a simple command to multiple users, without additional parameters.

Parameters

cmd [String]: the identifier of the response.

recipients [List<User>]: the list of Users to send the response to.

void
sendResponse(String cmd, ISFSObject params, User recipient)

Sends a response to a specific user, including a number of additional parameters.

Parameters

cmd [String]: the identifier of the response.

params [SFSObject]: an object containing the parameters to be sent to the recipient.

recipient [User]: the User to send the response to.

void
sendResponse(String cmd, ISFSObject params, List<User> recipients)

Sends a response to multiple users, including a number of additional parameters.

Parameters

cmd [String]: the identifier of the response.

params [SFSObject]: an object containing the parameters to be sent to the recipient.

recipients [List<User>]: the list of Users to send the response to.

void
trace(ExtensionLogLevel level, Object ... args)

Prints a message to the console and log files using the specified logging level.

Parameters

level [ExtensionLogLevel]: the level of the message to be logged.

args [Object...]: any number of strings/objects to log.

» Client side

BaseModule class

Type Constructor
none
constructor(commandsPrefix)

The BaseModule class constructor. Must be invoked with super in the costructor of the client side custom module controller.

Parameters

commandsPrefix [string]: the prefix to be used to deliver the requests to the appropriate server side request handler.

Type Property
Object
idData

An object containing the module configuration parameters:

  • id: the module identifier
  • name: the module name
  • description: the module description
  • icon: the module icon in SVG format
  • tag: the module identifier in kebab-case

ShellController
shellCtrl

A reference to the controller class of the AdminTool shell, providing useful general methods to display notification, alerts, etc. Check the class ShellController class API below.

SmartFox
smartFox

A reference to the SFS2X HTML5/JavaScript API instance managed by the AdminTool controller class. Provides access to all client API methods; check the API JSDoc for more information.

Type Method
none
initialize(idData, shellController)

Called when the module is loaded. This method can be overridden to perform specific initialization tasks. If overridden, the parent method must always be called through the super keyword, passing the received parameters.

Parameters

idData [Object]: an object containing the module configuration parameters (see idData property above).

shellController [ShellController]: a reference to the controller class of the AdminTool shell (see shellCtrl property above).

none
destroy()

Called when the module is unloaded. This method can be overridden to perform specific tasks on module unload (i.e. remove event listeners). If overridden, the parent method must always be called through the super keyword.

none
onExtensionCommand(cmd, data)

Called when a response from the server side request handler is received. This method must be overridden to execute the custom module logic.

Parameters

cmd [String]: the identifier of the response.

data [SFSObject]: an object containing the response payload.

none
onUptimeUpdated(values)

Called by the AdminTool shell controller once per second. This method can be overridden to display the uptime inside the custom module or make calculations based on the server uptime.

Parameters

values [Array]: an array containing, in order, the uptime days, hours, minutes and seconds.

none
sendExtensionRequest(command, data = null)

Sends a request to the module's server side request handler.

Parameters

command [String]: the identifier of the request.

data [SFSObject]: an optional object containing the parameters accompanying the request.

ShellController class

Type Method
none
logMessage(message, type = 'log')

Prints a message in the browser's console.

Parameters

message [String]: the message to be printed in the browser's console.

type [String]: the message importance level among info, warn, error, log (default).

none
removeDialog()

Removes all currently opened dialog or alert panels.

none
showConfirmWarning(text, confirmHandler)

Opens a modal panel and displays a warning message, asking for a confirmation. The panel shows an Ok button to confirm and a Cancel button to deny. The title of the panel is always "Warning".

Parameters

text [String]: the message to be displayed in the modal panel.

confirmHandler [Function]: a function to be called if the Ok button is clicked.

none
showNotification(title, message)

Shows a notification in the upper right corner of the AdminTool interface. The notification is removed automatically after a few seconds. The message is also logged in the browser's console.

Parameters

title [String]: the title of the notification.

message [String]: the message to be displayed in the notification; can contain html tags.

none
showSimpleAlert(text, isWarning = true)

Opens a modal panel and displays a message.

Parameters

text [String]: the message to be displayed in the modal panel.

isWarning [Boolean]: if true (default), the title of the modal panel will be "Warning", otherwise it will be "Information".

 

« Back to AdminTool's table of contents