Satori JavaScript Client Guide #

This client library guide will show you how to use the core Satori features in JavaScript.

Prerequisites #

Before proceeding ensure that you have:

The Satori client is packaged as part of the Nakama JavaScript SDK, but using Nakama is not required.

Full API documentation #

For the full API documentation please visit the API docs.

Installation #

The client is available on:

If using NPM or Yarn just add the dependency to your package.json file:

1
2
yarn add "@heroiclabs/satori-js"
yarn install

After installing the client import it into your project:

1
import {Client} from "@heroiclabs/nakama-js"

In your main JavaScript function create a client object.

Updates #

New versions of the Satori JavaScript Client and the corresponding improvements are documented in the Release Notes.

Getting started #

Learn how to get started using the Satori Client to manage your live game.

Satori client #

The Satori Client connects to a Satori Server and is the entry point to access Satori features. It is recommended to have one client per server per game.

To create a client pass in your server connection details:

1
2
3
4
5
6
const apiKey = "apiKey";
const host = "127.0.0.1";
const port = 7450;
const useSsl = false;

const client = new Client(apiKey, host, port, useSsl);
API Keys
Create and rotate API keys in the Satori Settings page.

Configuring the request timeout length #

Each request to Satori from the client must complete in a certain period of time before it is considered to have timed out. You can configure how long this period is (in seconds) by setting the timeout value when creating the client:

1
2
3
4
5
6
7
const apiKey = "apiKey";
const host = "127.0.0.1";
const port = 7450;
const useSsl = false;
const timeout = 1200000;

const client = new Client(apiKey, host, port, useSsl, timeout);

Authentication #

Authenticate users using the Satori Client via their unique ID.

When authenticating, you can optionally pass in any desired defaultProperties and/or customProperties to be updated. If none are provided, the properties remain as they are on the server.

1
2
3
4
5
6
7
8
9
const id = "<UniqueIdentity>";

client.authenticate(id)
  .then((session) => {
    console.log('Authenticated successfully.', session);
  })
  .catch((error) => {
    console.log('Error authenticating.', error);
  });

When authenticated the server responds with an auth token (JWT) which contains useful properties and gets deserialized into a Session object.

Session lifecycle #

Sessions expire after five (5) minutes by default. Expiring inactive sessions is good security practice.

Satori provides ways to restore sessions, for example when players re-launch the game, or refresh tokens to keep the session active while the game is being played.

Use the auth and refresh tokens on the session object to restore or refresh sessions.

Store these tokens using your preferred storage mechanism such as localStorage:

1
2
localStorage.setItem('satori.auth_token', session.token);
localStorage.setItem('satori.refresh_token', session.refresh_token);

Restore a session without having to re-authenticate:

1
2
3
const token = localStorage.getItem('satori.auth_token')
const refreshToken = localStorage.getItem('satori.refresh_token');
const session = Session.restore(token, refreshToken);

Automatic session refresh #

The JavaScript client library includes a feature where sessions close to expiration are automatically refreshed.

This is enabled by default but can be configured when first creating the Satori client using the following parameters:

  • autoRefreshSession - Boolean value indicating if this feature is enabled, true by default

Manually refreshing a session #

Sessions can be manually refreshed.

1
client.sessionRefresh(session).then((session) => console.log('Session refreshed.', session));

Ending sessions #

Logout and end the current session:

1
client.logout(session).then(() => console.log('Logged out.'));

Experiments #

Satori Experiments allow you to test different game features and configurations in a live game.

List the current experiments for this user:

1
client.getExperiments(session).then((experimentsList) => console.log('Experiments:', experimentsList));

You can also specify an array of experiment names you wish to get:

1
client.getExperiments(session, ['ExperimentOne', 'ExperimentTwo']).then((experimentsList) => console.log('Experiments:', experimentsList));

Feature flags #

Satori feature flags allow you to enable or disable features in a live game.

Get a single flag #

Get a single feature flag for this user:

1
client.getFlag(session, 'flagName').then((flag) => console.log('Flag:', flag));

You can also specify a default value for the flag if a value cannot be found:

1
client.getFlag(session, 'flagName', 'defaultValue').then((flag) => console.log('Flag:', flag));

Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.

Get a single default flag #

Get a single default flag for this user:

1
client.getFlagDefault(session, 'flagName').then((flag) => console.log('Flag:', flag));

Similar to the GetFlagAsync method, you can also provide a default value for default flags:

1
client.getFlagDefault(session, 'flagName', 'defaultValue').then((flag) => console.log('Flag:', flag));

Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.

Listing identity flags #

List the available feature flags for this user:

1
client.getFlags(session).then((flags) => console.log('Flags:', flags));

You can also specify an array of flag names you wish to get:

1
client.getFlags(session, ['flagOne', 'flagTwo']).then((flags) => console.log('Flags:', flags));

Listing default flags #

List all default feature flags:

1
client.getFlagsDefault().then((flags) => console.logs('Flags:', flags));

Events #

Satori Events allow you to send data for a given user to the server for processing.

Metadata Limits
The maximum size of the metadata field is 4096 bytes.

Sending single events #

1
client.event(session, { name: 'gameFinished', value: Date().now, metadata: { hello: 'world' }}).then(() => console.log('Event sent'));

Sending multiple events #

1
2
3
4
client.events(session, [
  { name: 'adStarted', value: Date().now, metadata: { meta: 'data' }},
  { name: 'appLaunched', value: Date().now, metadata: { meta: 'data' }}
]).then(() => console.log('Events sent));

Live events #

Satori Live Events allow you to deliver established features to your players on a custom schedule.

List all available live events:

1
client.getLiveEvents(session).then((liveEvents) => console.log('Live events:', liveEvents));

Identities #

Satori Identities identify individual players of your game and can be enriched with custom properties.

List an identity’s properties #

1
client.listProperties(session).then((properties) => console.log('Properties:', properties));

Update an identity’s properties #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const defaultProperties = {
  'defaultPropertyKey': 'defaultPropertyValue',
  'anotherDefaultPropertyKey': 'anotherDefaultPropertyValue'
};

const customProperties = {
  'customPropertyKey': 'customPropertyValue',
  'anotherCustomPropertyKey': 'anotherCustomPropertyValue'
};

client.updateProperties(session, defaultProperties, customProperties).then(() => console.log('Properties updated'));

You can immediately reevaluate the user’s audience memberships upon updating their properties by passing recompute as true:

1
2
const recompute = true;
client.updateProperties(session, defaultProperties, customProperties, recompute).then(() => console.log('Properties updated'));

Identifying a session with a new ID #

If you want to submit events to Satori before a user has authenticated with the game server backend (e.g. Nakama) and has a User ID, you should authenticate with Satori using a temporary ID, such as the device’s unique identifier or a randomly generated one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// If the user's device ID is already stored, grab that - alternatively get the System's unique device identifier.
let deviceId = localStorage.getItem('deviceId');

// If the device identifier doesn't exist then generate a unique one.
if (!deviceId) {
  deviceId = crypto.randomUUID();
}

// Save the user's ID to local storage so it can be retrieved during a later play session for re-authenticating.
localStorage.setItem('deviceId', deviceId);

// Authenticate with Satori
client.authenticate(deviceId)
        .then(session => {
          console.log('Authenticated with Satori');
        })
        .catch(error => {
          console.log('Error authenticating', error);
        });

You can then submit events before the user has authenticated with the game backend.

1
client.event(session, { name: 'gameLaunched', value: Date().now }).then(() => console.log('event sent'));

The user would then authenticate with the game backend and retrieve their User ID.

1
2
3
4
nakamaClient.authenticateCustom('<nakamaCustomId>')
  .then(nakamaSession => {
    const nakamaUserId = nakamaSession.userId;
  });

Once a user has successfully authenticated, you should then call IdentifyAsync to enrich the current session and return a new session that should be used for submitting future events.

1
2
3
4
5
client.identify(session, nakamaUserId, defaultProperties, customProperties)
  .then(newSession => console.log('New session', newSession))
  .catch(error => {
    console.log('Error calling identify', error);
  });

Note that the old session is no longer valid and cannot be used after this.

Deleting an identity #

Delete the calling identity and its associated data:

1
client.deleteIdentity(session);