SFS2X Docs / ExtensionsJS / quick-start
» JavaScript Extensions: a quick start
SmartFoxServer 2X allows developers to write their server side code to extend the server's capabilities, integrate it with other technologies and manage the game state in a centralized place.
The diagram above shows a simplified version of the server stack, where at the bottom client connect via a number of protocols and can invoke both the public server API and the custom calls exposed by the game developer via Extensions.
In simple terms SmartFoxServer Extensions are to games what PHP/JSP pages are to websites. They allow for custom code to run on the server side and add entirely new application logic.
In SmartFoxServer 2X Extensions can be attached to a Zone or a Room, depending on the scope of the Extension itself. A Room Extension has a smaller scope, dealing with events and calls of that Room, while a Zone Extension can listen for a much larger number of events and control all Rooms.
» Let's write an Extension
function init() { addRequestHandler("sum", onSumRequest); trace("Simple JS Example inited"); } function destroy() { trace("Simple JS Example destroyed"); } function onSumRequest(params, sender) { var n1 = params.getInt("n1"); var n2 = params.getInt("n2"); var response = new SFSObject(); response.putInt("res", n1 + n2); send("sum", response, [sender]); }
Every Extension starts with an init() method, which is called by the server at start up. Here we can initialize global objects or data structures and register event handlers. In this example we declare a command called "sum" which is handled by our onSumRequest() function.
The function extracts the parameters from an SFSObject, used to transport data between client and server, and sends the response back. You can think of an SFSObject as a simple dictionary object to store key/value pairs, with the added benefit of declaring exactly what type each data is. This in turn allows to optimize the network usage.
More information on SFSObject is available in this document; also check the JSDoc.
Extensions can also declare a destroy() method but it's not mandatory. It is required only for deallocating resources that require manual release such as active database connections, open files, or scheduled tasks.
As a side note, the trace() function allows you to output log information on the server side.
Let's now build a small client, connect to the server and test our custom server code:
public class ExtensionTester : MonoBehaviour { private SmartFox sfs; void Start() { // Create SmartFox client instance sfs = new SmartFox(); // Add event listeners sfs.AddEventListener(SFSEvent.CONNECTION, OnConnection); sfs.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost); sfs.AddEventListener(SFSEvent.LOGIN, OnLogin); sfs.AddEventListener(SFSEvent.LOGIN_ERROR, OnLoginError); sfs.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse); // Set connection parameters ConfigData cfg = new ConfigData(); cfg.Host = "127.0.0.1"; cfg.Port = 9933; cfg.Zone = "BasicExamples"; // Connect to SFS2X sfs.Connect(cfg); } void Update() { if (sfs != null) sfs.ProcessEvents(); } private void OnConnection(BaseEvent evt) { if ((bool)evt.Params["success"]) { Debug.Log("Connected"); // Send login request sfs.Send(new Sfs2X.Requests.LoginRequest("")); } else Debug.LogError("Connection failed"); } private void OnConnectionLost(BaseEvent evt) { Debug.Log("Disconnected"); } private void OnLogin(BaseEvent evt) { Debug.Log("Logged in as: " + sfs.MySelf.name); // Send test request to Extension ISFSObject params = SFSObject.NewInstance(); params.PutInt("n1", 25); params.PutInt("n2", 17); sfs.Send(new Sfs2x.Requests.ExtensionRequest("sum", params)); } private void OnLoginError(BaseEvent evt) { Debug.LogError("Login error: " + (string)evt.Params["errorMessage"]); } private void OnExtensionResponse(BaseEvent evt) { // Retrieve response object ISFSObject responseParams = (SFSObject)evt.Params["params"]; Debug.Log("Result: " + responseParams.GetInt("res")); } }
window.onload = function() { // Set connection parameters var config = {}; config.host = "127.0.0.1" config.port = 8080; config.zone = "BasicExamples"; // Create SmartFox client instance sfs = new SFS2X.SmartFox(config); // Add event listeners sfs.addEventListener(SFS2X.SFSEvent.CONNECTION, onConnection, this); sfs.addEventListener(SFS2X.SFSEvent.CONNECTION_LOST, onConnectionLost, this); sfs.addEventListener(SFS2X.SFSEvent.LOGIN, onLogin, this); sfs.addEventListener(SFS2X.SFSEvent.LOGIN_ERROR, onLoginError, this); sfs.addEventListener(SFS2X.SFSEvent.EXTENSION_RESPONSE, onExtensionResponse, this); // Connect to SFS2X sfs.connect(); }; function onConnection(evtParams) { if (evtParams.success) { console.log("Connected"); // Send login request sfs.send(new SFS2X.LoginRequest("")); } else console.error("Connection failed"); } function onConnectionLost(evtParams) { console.log("Disconnected"); } function onLogin(evtParams) { console.log("Logged in as: " + sfs.mySelf.name); // Send test request to Extension var params = new SFS2X.SFSObject(); params.putInt("n1", 25); params.putInt("n2", 17); sfs.send(new SFS2X.ExtensionRequest("sum", params)); } function onLoginError(evtParams) { console.log("Login error: " + evtParams.errorMessage); } function onExtensionResponse(evtParams) { // Retrieve response object var responseParams = evtParams.params; console.log("Result: " + responseParams.getInt("res")); }
public class ExtensionTester extends Sprite { private var sfs:SmartFox public function ExtensionTester() { // Create SmartFox client instance sfs = new SmartFox(); // Add event listeners sfs.addEventListener(SFSEvent.CONNECTION, onConnection); sfs.addEventListener(SFSEvent.CONNECTION_LOST, onConnectionLost); sfs.addEventListener(SFSEvent.LOGIN, onLogin); sfs.addEventListener(SFSEvent.LOGIN_ERROR, onLoginError); sfs.addEventListener(SFSEvent.EXTENSION_RESPONSE, onExtensionResponse); // Set connection parameters var cfg:ConfigData = new ConfigData(); cfg.host = "127.0.0.1"; cfg.port = 9933; cfg.zone = "BasicExamples"; // Connect to SFS2X sfs.connectWithConfig(cfg); } private function onConnection(evt:SFSEvent):void { if (evt.params.success) { trace("Connected"); // Send login request sfs.send(new LoginRequest("")); } else trace("Connection failed"); } private function onConnectionLost(evt:SFSEvent):void { trace("Disconnected"); } private function onLogin(evt:SFSEvent):void { trace("Logged in as: " + sfs.mySelf.name); // Send test request to Extension var params:ISFSObject = new SFSObject(); params.putInt("n1", 25); params.putInt("n2", 17); sfs.send(new ExtensionRequest("sum", params)); } private function onLoginError(evt:SFSEvent):void { trace("Login error: " + evt.params.errorMessage); } private function onExtensionResponse(evt:SFSEvent):void { // Retrieve response object var responseParams:ISFSObject = evt.params.params as SFSObject; trace("Result: " + responseParams.getInt("res")); } }
In the onLogin() function we wrap our parameters and send the command via the ExtensionRequest object. To handle the server response we make sure to register for the EXTENSION_RESPONSE event and read the result using the same key(s) used on the server side.
» Server side events
In addition to handling client requests a server-side Extension can also listen for a number of Server's events, such as login events, join room events etc.
Listening for server events is as simple as handling client requests: we just create a function and register it as event handler. Here's a basic example with no purpose other than demonstrating the functionality:
function init() { addEventHandler(SFSEventType.USER_JOIN_ZONE, onNewUser); trace("Simple JS Example inited"); } function destroy() { trace("Simple JS Example destroyed"); } function onNewUser(event) { user = event.getParameter(SFSEventParam.USER); trace("Welcome new user: " + user.getName()); }
Each event provides a number of parameters that can be accessed as shown in the code. For a full list of events, check the documentation here; also follow the link to the Javadoc to get the list of parameters provided by each event.
» Accessing the API
To assist you with the development every JavaScript Extension provides a number of methods and API objects directly in the global scope.
A few of the most important are:
Member | Description |
addRequestHandler() | Allows to register a new custom client request |
addEventHandler() | Allows to register a new custom server event |
getParentZone() | Get a reference to the parent Zone |
getParentRoom() | Get a reference to the parent Room (only if the Extension is attached to a Room) |
getCurrentFolder() | Returns the current Extension folder
|
SFSObject | A key/value, dictionary-like object used to send and receive data between client and server |
SFSArray | An indexed, array-like object used to send and receive data between client and server |
Also the global scope provides access to a rich set of API, categorized in several groups:
Function | Description |
getApi() | Provides all main calls to create Rooms, set User/Room variables, join users, chat messages, banning/kicking etc... |
getBuddyApi() | Buddy related functionalities: add/remove/create etc... |
getGameApi() | Match making, Invitations, Quick join, public/private games etc... |
getMMOApi() | Provide all features related to MMORooms |
getFileApi() | Wide array of file system functions (move, copy, delete, create etc...) |
For example joining a user in a Room requires a few lines of code:
function joinUserInRoom(user, room) { getApi().joinRoom(user, room, "", false); }
You can find all the details about the global Extension objects in the JSDoc.
» Extension deployment
Finally we can take a look at how we can deploy our code to the server and run it. To create an extension you will need to create a new folder under SFS2X/extensions/
Inside this directory we'll add the JavaScript files for our extension. Next we open the AdminTool, select the Zone Configurator and click the Zone Extension tab.
First make sure the Type dropdown is set to JavaScript. then from the top dropdown select the Extension folder and finally the "Main class" which in this case is our main JavaScript file. We can now save and restart the server to activate the changes.
The Reload mode option set to AUTO allows to auto-reload an Extension every time a change is made to the main file, thus making it very quick to reload our code on the fly. This is convenient for development and testing but in production it is best to set this to MANUAL to avoid accidental reloads.
NOTE: if you want to attach the Extension to a specific Room instead of a Zone you can repeat the same exact process, this time selecting the target Room from the Zone Configurator.
» Next steps
Once you have familiarized with these concepts we highly recommend to move on to the next article in this section which will give you a more in-depth outlook on Extension development using JavaScript.