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

 

» Sign Up Assistant | Basic concepts

The Sign Up Assistant component works by plugging it into your server Extension and adding new commands that the client can invoke to access its services.

In a nutshell, the component requires three steps:

  1. configure the DBManager in your application's Zone, so that you can communicate with your local DB
  2. instantiate the component in the init() method of your Extension
  3. configure the component according to your needs
NOTE
Throughout this tutorial we will make several references to the MySQL database. You can use any other database engine such as MSSQL, MS Access, Postgres, HSQLDB, Derby, etc. The component described here works via JDBC, ensuring portability to many other platforms.

» Simple use case

Let's start with a simple scenario: we are launching a brand new game and we need a simple registration system for our players. We'll start with a database by installing a local copy of MySQL, or any similar product of your choice.

The next step will be to configure the database access in SmartFoxServer 2X by opening the AdminTool, selecting the Zone Configurator and selecting the Database Manager tab (you can find more details on how to proceed in this recipe).

» Setting up the database table

For the sake of simplicity we'll create a basic database table called users with this structure:

field name type length index
id int -- primary, autoincrement
username varchar 30 --
password varchar 30 --
email varchar 30 --

We'll use three string fields to hold the essential information for our players (name, password and email) plus the id field, of type integer, as primary index.

» Using the component in our Extension

This is the code that we will use in our main Extension class to enable the Sign Up Assistant:

	public class MyGameExtension extends SFSExtension
	{
		private SignUpAssistantComponent suac;
		
		@Override
		public void init()
		{
			suac = new SignUpAssistantComponent();
			addRequestHandler(SignUpAssistantComponent.COMMAND_PREFIX, suac);
		}
		
		@Override
		public void destroy()
		{
		    super.destroy();
		}
	}

This looks pretty straightforward, isn't it?

Before we discuss what's going on behind the scenes let's complete the picture by looking at the code that we will use from the client side to send the registration data.

	// Define SignUp extension command
    private string CMD_SUBMIT = "$SignUp.Submit";

	...
	
	// Initialize SFS2X client and add listeners
	SmartFox sfs = new SmartFox();
	
    sfs.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);
	
	...

	/**
	 * Send registration data.
	 */
    private void sendSignUpData()
    {
        ISFSObject sfso = SFSObject.NewInstance();
        sfso.PutUtfString("username", tf_username.text);
        sfso.PutUtfString("password", tf_password.text);
        sfso.PutUtfString("email", tf_email.text);
        
        sfs.Send(new Sfs2X.Requests.ExtensionRequest(CMD_SUBMIT, sfso));
    }

	/**
	 * Process extension response.
	 */
	private void OnExtensionResponse(BaseEvent evt)
    {
        string cmd = (string)evt.Params["cmd"];
		SFSObject sfso = (SFSObject)evt.Params["params"];
        
        if (cmd == CMD_SUBMIT)
        {
            if (sfso.getBool("success"))
                Console.WriteLine("Success, thanks for registering");
            else
                Console.WriteLine("SignUp error:" + sfso.GetUtfString("errorMessage"));
        }
    }
	// Define SignUp extension command
    var CMD_SUBMIT = "$SignUp.Submit";

	...
	
	// Initialize SFS2X client and add listeners
	var sfs = new SFS2X.SmartFox();
	
    sfs.addEventListener(SFS2X.SFSEvent.EXTENSION_RESPONSE, onExtensionResponse, this);
	
	...

	/**
	 * Send registration data.
	 */
    function sendSignUpData()
    {
        var sfso = new SFS2X.SFSObject();
        sfso.putUtfString("username", document.getElementById("tf_username").value);
        sfso.putUtfString("password", document.getElementById("tf_password").value);
        sfso.putUtfString("email", document.getElementById("tf_email").value);
        
        sfs.send(new SFS2X.ExtensionRequest(CMD_SUBMIT, sfso));
    }

	/**
	 * Process extension response.
	 */
	function onExtensionResponse(evt)
    {
        var cmd = evt.cmd;
        var sfso = evt.params;
        
        if (cmd == CMD_SUBMIT)
        {
            if (sfso.getBool("success"))
                console.log("Success, thanks for registering");
            else
                console.warn("SignUp error:" + sfso.getUtfString("errorMessage"));
        }
    }
	// Define SignUp extension command
    var CMD_SUBMIT:String = "$SignUp.Submit";

	...
	
	// Initialize SFS2X client and add listeners
	var sfs:SmartFox = new SmartFox();
	
    sfs.addEventListener(SFSEvent.EXTENSION_RESPONSE, onExtensionResponse);
	
	...

	/**
	 * Send registration data.
	 */
    private function sendSignUpData():void
    {
        var sfso:SFSObject = new SFSObject();
        sfso.putUtfString("username", tf_username.text);
        sfso.putUtfString("password", tf_password.text);
        sfso.putUtfString("email", tf_email.text);
        
        sfs.send(new ExtensionRequest(CMD_SUBMIT, sfso));
    }

	/**
	 * Process extension response.
	 */
	private function onExtensionResponse(evt:SFSEvent):void
    {
        var cmd:String = evt.params["cmd"];
        var sfso:ISFSObject = evt.params["params"];
        
        if (cmd == CMD_SUBMIT)
        {
            if (sfso.getBool("success"))
                trace("Success, thanks for registering");
            else
                trace("SignUp error:" + sfso.getUtfString("errorMessage"));
        }
    }

The sendSignUpData method takes the username, password and email values from three text fields that we are showing on screen, wrap them in an SFSObject and send them to the Extension using the $SignUp.Submit command, which in turn will execute the Sign Up Assistant request handler.

In the onExtensionResponse method we handle the server response by either showing a success message or displaying an error message in case something went wrong. For example if the username or password is missing, an error will be sent back to the client.

» Where is the magic?

At this point you might be wondering... wait a minute! How does the component know what to do with the database, which table to use and what are the field names?

The Sign Up Assistant component is fully configurable by accessing its configuration object via the getConfig() method. From there you can access many parameters that can be fine tuned according to your requirements.

Let's see a few of the basic properties in the configuration object:

property name type default value description
signUpTable String "users" The name of the sign up table to use
idField String "id" The name of the field used to store the record id
userNameField String "username" The name of the field used to store the user name
passwordField String "password" The name of the field used to store the password
emailField String "email" The name of the field used to store the email
checkForDuplicateEmails Bool true Checks for duplicate emails
checkForDuplicateUserNames Bool true Checks for duplicate user names

It turns out that the "magic" we mentioned earlier is just that we used the default values of the Sign Up Assistant to create our database. This way we don't even need to configure it in our Extension code. It goes without saying that you can customize any of these fields (and many, many more) as you prefer and according to your database table and field names.

Just to be clear, here is an example of how you can configure the component in your Extension to use a different table and field names:

	@Override
	public void init()
	{
		suac = new SignUpAssistantComponent();
		
		suac.getConfig().signUpTable = "signup";
		suac.getConfig().userNameField = "user_name";
		suac.getConfig().passwordField = "user_pword";
		suac.getConfig().emailField = "user_email";
		suac.getConfig().checkForDuplicateEmails = false;
		
		addRequestHandler(SignUpAssistantComponent.COMMAND_PREFIX, suac);
	}

» A more advanced use case

Let's suppose that our sign up process requires more information beyond the name, password and email fields. We will extend our users table by adding two extra fields: age and country.

field name type length index
id int -- primary,
autoincrement
username varchar 30 --
password varchar 30 --
email varchar 30 --
age int -- --
country varchar 30 --

Now that we have updated the database we would like not only to populate the new fields but also to apply custom validation to the process. Also, while we are at it, we would like to make sure that passwords are at least 8 characters long, for better security, and similarly we would like to impose limits in the minimum and maximum lenght of the user name.

Let's take a look at this new code:

	@Override
	public void init()
	{
		suac = new SignUpAssistantComponent();
		suac.getConfig().extraFields = Arrays.asList("country", "age");
		
		// Set limits for min/max name and password length
		suac.getConfig().minUserNameLength = 4;
		suac.getConfig().maxUserNameLength = 30;
		suac.getConfig().minPasswordLength = 8;
		suac.getConfig().maxPasswordLength = 30;
		
		// Add a pre-process plugin for custom validation
		suac.getConfig().preProcessPlugin = new ISignUpAssistantPlugin()
		{
			@Override
			public void execute(User user, ISFSObject params, SignUpConfiguration config) throws SignUpValidationException
			{
				Integer age = params.getInt("age");
			    String country = params.getUtfString("country");
	
			    if (age == null)
			    	throw new SignUpValidationException(SignUpErrorCodes.CUSTOM_ERROR, "The age is missing");
			
				if (age < 14)
			    	throw new SignUpValidationException(SignUpErrorCodes.CUSTOM_ERROR, "You must be at least 14 years old to access this game");
				
				if (country == null || country.length() < 2)
			    	throw new SignUpValidationException(SignUpErrorCodes.CUSTOM_ERROR, "Pleas specify your country");
			}
		};
		
		addRequestHandler(SignUpAssistantComponent.COMMAND_PREFIX, suac);
	}

We can declare any number of extra fields by passing a List of field names to the configuration's extraField parameter. Each name must match the respective database field name. Once this is done the Sign Up Assistant will take any value passed by the client in the SFSObject with those field names and store it in the database.

In the next few lines of our new code we have configured the minimum and maximum amount of characters allowed for the user name and password. Client values that are off range will be refused and an error will be sent back to the user.

Then we proceed by passing a custom plugin that contains our particular validation logic for the age and country fields. The Sign Up Assistant component provides a convenient interface, ISignUpAssistantPlugin, that can be implemented and executed before (pre) or after (post) the data gets written to the database.

In this case we are interested in checking that the age and country satisfy our rules before the data goes to the database. This way we can interrupt the sign up process by throwing a SignUpValidationException which in turn will send the error back to the client.

In the code example above we make use of an anonymous inner class declaration. If you're not familiar with this syntax in Java, you can take a look at this external article that clarifies the concept.

» The client side

While the server side example has more code than the initial snippet, on the client side the code doesn't change very much from the first example. This is a snippet that sends a valid user registration request:

	// Define SignUp extension command
    private string CMD_SUBMIT = "$SignUp.Submit";

	...
	
	// Initialize SFS2X client and add listeners
	SmartFox sfs = new SmartFox();
	
    sfs.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);
	
	...

	/**
	 * Send registration data.
	 */
    private void sendSignUpData()
    {
        ISFSObject sfso = SFSObject.NewInstance();
        sfso.PutUtfString("username", "Animal");
	    sfso.PutUtfString("password", "drumlover");
	    sfso.PutUtfString("email", "animal@muppets.net");
	    sfso.PutInt("age", 35);
	    sfso.PutUtfString("country", "MuppetsLand");
        
        sfs.Send(new Sfs2X.Requests.ExtensionRequest(CMD_SUBMIT, sfso));
    }

	/**
	 * Process extension response.
	 */
	private void OnExtensionResponse(BaseEvent evt)
    {
        string cmd = (string)evt.Params["cmd"];
		SFSObject sfso = (SFSObject)evt.Params["params"];
        
        if (cmd == CMD_SUBMIT)
        {
            if (sfso.getBool("success"))
                Console.WriteLine("Success, thanks for registering");
            else
                Console.WriteLine("SignUp error:" + (string)evt.Params["errorMessage"]);
        }
    }
	// Define SignUp extension command
    var CMD_SUBMIT = "$SignUp.Submit";

	...
	
	// Initialize SFS2X client and add listeners
	var sfs = new SFS2X.SmartFox();
	
    sfs.addEventListener(SFS2X.SFSEvent.EXTENSION_RESPONSE, onExtensionResponse, this);
	
	...

	/**
	 * Send registration data.
	 */
    function sendSignUpData()
    {
        var sfso = new SFS2X.SFSObject();
        sfso.putUtfString("username", "Animal");
	    sfso.putUtfString("password", "drumlover");
	    sfso.putUtfString("email", "animal@muppets.net");
	    sfso.putInt("age", 35);
	    sfso.putUtfString("country", "MuppetsLand");
        
        sfs.send(new SFS2X.ExtensionRequest(CMD_SUBMIT, sfso));
    }

	/**
	 * Process extension response.
	 */
	function onExtensionResponse(evt)
    {
        var cmd = evt.cmd;
        var sfso = evt.params;
        
        if (cmd == CMD_SUBMIT)
        {
            if (sfso.getBool("success"))
                console.log("Success, thanks for registering");
            else
                console.warn("SignUp error:" + evt.errorMessage);
        }
    }
	// Define SignUp extension command
    var CMD_SUBMIT:String = "$SignUp.Submit";

	...
	
	// Initialize SFS2X client and add listeners
	var sfs:SmartFox = new SmartFox();
	
    sfs.addEventListener(SFSEvent.EXTENSION_RESPONSE, onExtensionResponse);
	
	...

	/**
	 * Send registration data.
	 */
    private function sendSignUpData():void
    {
        var sfso:SFSObject = new SFSObject();
        sfso.putUtfString("username", "Animal");
	    sfso.putUtfString("password", "drumlover");
	    sfso.putUtfString("email", "animal@muppets.net");
	    sfso.putInt("age", 35);
	    sfso.putUtfString("country", "MuppetsLand");
        
        sfs.send(new ExtensionRequest(CMD_SUBMIT, sfso));
    }

	/**
	 * Process extension response.
	 */
	private function onExtensionResponse(evt:SFSEvent):void
    {
        var cmd:String = evt.params["cmd"];
        var sfso:ISFSObject = evt.params["params"];
        
        if (cmd == CMD_SUBMIT)
        {
            if (sfso.getBool("success"))
                trace("Success, thanks for registering");
            else
                trace("SignUp error:" + evt.params["errorMessage"]);
        }
    }

» Password mode

In the example we have discussed so far the user password is stored in plain text in the database. If you prefer to store passwords in an encrypted fashion you can change the default configuration settings via this parameter:

	@Override
	public void init()
	{
		suac = new SignUpAssistantComponent();
	    ...
		suac.getConfig().passwordMode = PasswordMode.MD5;
	    ...
	}

By using the MD5 we will store a hashed version of the password which requires the database password field to be 32 characters long. The MD5 hash is pretty secure and not easily reversible, although decryption methods exist. In general a non trivial password made of alphanumeric characters and numbers will be very difficult to crack.

If you plan to store hashed passwords on the server side you will need to pre-hash your password on the client side too when logging in. This is easily done in C# and ActionScript 3 via a helper found in the PasswordUtil class. In JavaScript you can use any MD5 library. This is an example of how it should work:

	string userName = "testName";
	string userPass = "testPass";
	
	string md5Pass = PasswordUtil.MD5Password(userPass);
	
	sfs.Send(new Sfs2X.Requests.LoginRequest(userName, md5Pass, sfs.Config.Zone));
	var userName = "testName";
	var userPass = "testPass";
	
	var md5Pass = SFS2X.PasswordUtil.md5Password(userPass);
	
	sfs.send(new SFS2X.LoginRequest(userName, md5Pass, null, sfs.config.zone));
	var userName:String = "testName";
	var userPass:String = "testPass";
	
	var md5Pass:String = PasswordUtil.md5Password(userPass);
	
	sfs.send(new LoginRequest(userName, md5Pass, sfs.config.zone));

» Wrapping up

So far we have learned how to configure the basic component's settings, how to map custom database fields, fine tuning the built-in validators and provide our own custom logic.

We can now learn how to work with confirmation emails and user account activations, which is the topic of the next part in this tutorial.

Choose your next destination: