Master Server Kit Database

From gamevanilla wiki
Jump to: navigation, search

Introduction

Master Server Kit includes an authentication system that can be used to securely log players into the master server. This system can be bypassed if you are not interested in the authentication functionality (the Allow guests option in the Master Server component controls this).

The authentication system uses a database for storing the players' data. The kit provides four default database implementations:

There is also a "no-database" implementation included that is an in-memory alternative with no persistent data storage between sessions that can be very useful for testing and prototyping purposes, as it does not require setting up a database.

Please note the provided MySQL implementation has been developed and tested with MySQL Workbench; using a different tool will likely require changes on your side.

By default, the SQLite implementation is used. You can easily switch the database implementation used by the DatabaseService class:

public static class DatabaseService
{
    /// <summary>
    /// Default database provider.
    /// </summary>
    private static IDatabaseProvider database = new SQLiteProvider();

    // ...
}

Replace the database line with

private static IDatabaseProvider database = new MongoDBProvider();

in order to use the MongoDB database.

Replace the database line with

private static IDatabaseProvider database = new LiteDBProvider();

in order to use the LiteDB database.

If you are interested in using a different database, it is easy to do so by creating your own provider. The provider needs to implement the IDatabaseProvider interface as appropriate:

/// <summary>
/// Custom database provider implementation.
/// </summary>
public class CustomDBProvider : IDatabaseProvider
{
    /// <summary>
    /// Performs any initialization-related logic.
    /// </summary>
    public void InitializeDatabase()
    {
        // ...
    }

    /// <summary>
    /// Registers a new user with the specified properties in the system.
    /// </summary>
    /// <param name="email">The new user's email address.</param>
    /// <param name="username">The new user's name.</param>
    /// <param name="password">The new user's password.</param>
    /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    /// <returns>Async operation for the request.</returns>
    public IEnumerator Register(string email, string username, string password, Action<string> onSuccess, Action<RegistrationError> onError)
    {
        // ...
    }

    /// <summary>
    /// Logs the specified user in the system.
    /// </summary>
    /// <param name="username">The user's name.</param>
    /// <param name="password">The user's password.</param>
    /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    /// <returns>Async operation for the remote request.</returns>
    public IEnumerator Login(string username, string password, Action<string> onSuccess, Action<LoginError> onError)
    {
        // ...
    }

    /// <summary>
    /// Gets the specified integer property from the specified user.
    /// </summary>
    /// <param name="username">The user's name.</param>
    /// <param name="key">The property's key.</param>
    /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    /// <returns>Async operation for the remote request.</returns>
    public IEnumerator GetIntProperty(string username, string key, Action<int> onSuccess, Action<string> onError)
    {
        // ...
    }

    /// <summary>
    /// Sets the specified integer property from the specified user to the specified value.
    /// </summary>
    /// <param name="username">The user's name.</param>
    /// <param name="key">The property's key.</param>
    /// <param name="value">The property's value.</param>
    /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    /// <returns>Async operation for the remote request.</returns>
    public IEnumerator SetIntProperty(string username, string key, int value, Action<int> onSuccess, Action<string> onError)
    {
        // ...
    }

    /// <summary>
    /// Gets the specified string property from the specified user.
    /// </summary>
    /// <param name="username">The user's name.</param>
    /// <param name="key">The property's key.</param>
    /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    /// <returns>Async operation for the remote request.</returns>
    public IEnumerator GetStringProperty(string username, string key, Action<string> onSuccess, Action<string> onError)
    {
        // ...
    }

    /// <summary>
    /// Sets the specified string property from the specified user to the specified value.
    /// </summary>
    /// <param name="username">The user's name.</param>
    /// <param name="key">The property's key.</param>
    /// <param name="value">The property's value.</param>
    /// <param name="onSuccess">Delegate to be executed when the request is successful.</param>
    /// <param name="onError">Delegate to be executed when the request is not successful.</param>
    /// <returns>Async operation for the remote request.</returns>
    public IEnumerator SetStringProperty(string username, string key, string value, Action<string> onSuccess, Action<string> onError)
    {
        // ...
    }
}

Note these functions are intended to be called asynchronously as coroutines. This is particularly convenient when interacting with a database, as there will usually be a degree of latency when accessing it (or maybe the database is not accessed directly but trough a web service instead; the provided MongoDB implementation does precisely this). As an example:

StartCoroutine(DatabaseService.GetStringProperty(
    username,
    "inventory",
    result => { Debug.Log(result); },
    error => { Debug.Log(error); }
));

Node.js + MongoDB

The following section applies only to the MongoDB database implementation. This implementation does not access the database directly, but via a server that implements a REST-based API. This server is written in Node.js/Express and uses a MongoDB database, so a fundamental knowledge of these technologies is recommended.

Initial setup

In order to set up the server on your machine, the first thing you need to do is to install the required dependencies:

The latest stable releases work just fine.

The next step is to unzip the contents of the PersistentDataServer/PersistentDataServer.zip file to a desired location on your machine (be careful not to extract it inside your Unity project, as that will cause build errors). The server files have the following structure:

  • app/ folder: Contains the complete source code of the server.
  • package.json file: Contains a list of all the dependencies for the server.
  • server.js file: Main source code file for the server.

You will need to run this command from a terminal

npm install

the first time in order to install the required dependencies (note that a node_modules folder will be created).

At this point, you should be able to run the server locally on your machine. The app/config/config.js is particularly interesting because it allows you to tweak the server configuration (the database name and secret, the number of cards contained in a purchasable card pack, etc.).

In order to run the server, you just need to run the following terminal command

node server.js

from the folder where you extracted the server files. Note that you also need to have a running instance of MongoDB (in a second terminal); in order to do that you need to run the following terminal command

mongod

You should now be able to use the server functionality from the demo game. Feel free to expand the provided functionality as you need for your game!

Important

If you are on a Windows machine, you may need to add the MongoDB folder (e.g., C:\Program Files\MongoDB\Server\3.2\bin) to your system PATH in order to run the mongod command from the terminal. Or, alternatively, just navigate to that location manually from the terminal and execute the command directly from there.

The following programs are incredibly useful for server development and testing:

Deploying the server in production

It is very convenient to have the server running locally on your machine during development, but sooner or later you are going to need to deploy it in a production environment.

You can choose between hosting your own server or use an existing, third-party hosting service. Well-known options for deploying the server in a production environment are AWS, Digital Ocean, Heroku and OpenShift. Choosing one over the other is a balance between ease of setup, experience of your team and pricing/scalability concerns that need to be carefully considered on every particular case. This means it is both a technical and a business decision that does not have a single, universally valid answer.

REST API

The server provides the following REST-based API:

POST /api/register

Creates a new player.

Parameters:

  • email (string): The player's email. Must be unique.
  • username (string): The player's name. Must be unique.
  • password (string): The player's password.

Example successful response:

{
    "status": "success",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1N2YyOTRmZjU3Mjk3ZTdjYzYxN2E2OGUiLCJlbWFpbCI6ImRwLmFob25lbjZAZ21haWwuY29tIiwidXNlcm5hbWUiOiJEYXZpZDYiLCJleHAiOjE0NzYxMjA0NDcsImlhdCI6MTQ3NTUxNTY0N30.i1_RzALcCLz0WSj-5ltoLHbtwxzHI4De4uRGbS81pxw"
}

POST /api/login

Logs an existing player into the server.

Parameters:

  • username (string): The player's name.
  • password (string): The player's password.

Example successful response:

{
    "status": "success",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1N2UyYzk5MTU3Mjk3ZTdjYzYxN2E2ODgiLCJlbWFpbCI6ImRwLmFob25lbkBnbWFpbC5jb20iLCJ1c2VybmFtZSI6IkRhdmlkIiwiZXhwIjoxNDc2MTIwNDgyLCJpYXQiOjE0NzU1MTU2ODJ9.tcfgwSRue2qxRtPvWcUsdtZtLsLKNGhXB61SWfwITMk",
    "username": "David"
}

All the API endpoints always return a "status" string indicating if the request was successful ("success") or not ("error"). In the case of an error, you can obtain more details by examining the "message" string.

The API provides security by leveraging JSON Web Tokens. When a player successfully logs into the server, he receives a token that uniquely identifies him on the system and that can be used to securely authenticate later calls to the API (if you decide to extend and/or customize it).

The code for accessing the REST API lives in the MongoDBProvider class, which you can find at the Core/Scripts/Database folder.