# Defold

**URL:** https://heroiclabs.com/docs/satori/client-libraries/defold/
**Summary:** The official Defold client handles all communication with the Satori server, enabling management of your live game via sending analytics events, updating properties, getting feature flags and experiments, and more.
**Keywords:** defold client guide, defold client, satori defold, authentication, user accounts, audiences, experiments, live events, feature flags, identities, liveops defold
**Categories:** satori, defold, client-libraries

---


# Satori Defold Client Guide

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

## Prerequisites

Before proceeding ensure that you have:

* Access to Satori server instance
* Installed the [Satori Defold SDK](#installation)

{{< note "important" >}}
The Satori client is packaged as part of the Nakama Defold SDK, but using Nakama **is not** required.
{{< / note >}}

### Full API documentation

For the full API documentation please visit the [API docs](https://defold.docs.heroiclabs.com/html/namespace_satori.html).


### Add the client to your project

Add the URL of a [client release .zip](https://github.com/heroiclabs/nakama-defold/releases) as a library dependency to `game.project`. The client will now show up as a `satori` folder in your project.


#### Updates

New versions of the Satori Defold Client and the corresponding improvements are documented in the [Release Notes](../../../nakama/getting-started/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:

```lua
local satori = require "satori.satori"
local config = {
    host = "127.0.0.1",
    api_key = "apiKey",
    use_ssl = false,
    port = 7450
}
local client = satori.create_client(config)
```

{{< note "important" "API Keys">}}
Create and rotate API keys in the [Satori Settings page](../../concepts/settings/#api-keys).
{{< / note >}}


## Authentication

Authenticate users using the Satori Client via their unique ID.

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

```lua
local uuid = require "nakama.util.uuid"
local session = require "nakama.session"

local id = uuid()
local custom_properties = nil
local default_properties = nil
local jwt = client.authenticate(custom_properties, default_properties, id)
if jwt.token then
  print("Authenticated successfully")
  session.store(jwt, "satori")
  client.set_bearer_token(jwt.token)
else
  print("Error authenticating")
end
```

When authenticated the server responds with an auth token (JWT) which gets deserialized into a `session` table containing the authentication token, a refresh token and timestamps when the tokens expire.

### 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 the tokens using the session module:

```lua
local session = require "nakama.session"
session.store(jwt, "satori")
```

Restore and refresh a session without having to re-authenticate, unless the session has expired in which case re-authentication is required:

```lua
local session = require "nakama.session"

local function authenticate()
  local jwt = session.restore("satori")
  -- refresh if the stored session token is about to expire!
  if jwt and session.is_token_expired_soon(jwt) and not session.is_refresh_token_expired(jwt) then
    print("Session has expired or is about to expire. Refreshing.")
    jwt = client.session_refresh(jwt.refresh_token)
  end
  -- login again if no session token is stored or it has expired
  if not jwt or session.is_refresh_token_expired(jwt) then
    print("Session does not exist or it has expired. Must reauthenticate.")
    local id = uuid()
    local custom_properties = nil
    local default_properties = nil
    jwt = client.authenticate(custom_properties, default_properties, id)
  end

  -- at this point we either have a valid session or no session at all
  if jwt then
    print("Session is valid. User is authenticated.")
    client.set_bearer_token(jwt.token)
    session.store(jwt, "satori")
    return true
  end

  print("No valid session. Authentication failed.")
  return false
end
```

### Ending sessions

Logout and end the current session:

```lua
local jwt = session.restore("satori")
client.authenticate_logout(jwt.refresh_token, jwt.token)
```

## Experiments

Satori [Experiments](../../concepts/experiments/) allow you to test different game features and configurations in a live game.

List the current experiments for this user:

```lua
local experiments = client.get_experiments()
```

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

```lua
local experiments = client.get_experiments({ "ExperimentOne", "ExperimentTwo" })
```

## Feature flags

Satori [feature flags](../../concepts/remote-configuration/) allow you to enable or disable features in a live game.

### Get a single flag

Get a single feature flag for this user:

```lua
local flag = client.get_flag({ "FlagName" })
```

### Get all flags

List the available feature flags for this user:

```lua
local flags = client.get_flag()
```


## Events

Satori [Events](../../concepts/performance-monitoring/understand-events/) allow you to send data for a given user to the server for processing.

{{< note "important" "Metadata Limits">}}
The maximum size of the `metadata` field is `4096` bytes.
{{< / note >}}

### Sending single events

```lua
local time = require "nakama.util.time"

local events_table = {
  satori.create_api_event(nil, nil, "gameFinished", time.now())
}
client.event(events_table)
```

### Sending multiple events

```lua
local time = require "nakama.util.time"

local events_table = {
  satori.create_api_event(nil, nil, "adStarted", time.now()),
  satori.create_api_event(nil, nil, "appLaunched", time.now())
}
client.event(events_table)
```

## Live events

Satori [Live Events](../../concepts/live-events/) allow you to deliver established features to your players on a custom schedule.

### List all available live events:

```lua
local live_events = client.get_live_events()
```

### Join explicit live events

```lua
await client.join_live_events_async(session, live_event_id);
```

## Identities

Satori [Identities](/docs/satori/concepts/segmentation/) identify individual players of your game and can be enriched with custom properties.

### List an identity's properties

```lua
local properties = client.list_properties()
```

### Update an identity's properties

```lua
local custom_properties = {
  { CustomPropertyKey = "CustomPropertyValue" },
  { AnotherCustomPropertyKey = "AnotherCustomPropertyValue" }
}

local default_properties = {
  { DefaultPropertyKey = "DefaultPropertyValue" },
  { AnotherDefaultPropertyKey = "AnotherDefaultPropertyValue" }
}

client.update_properties(custom_properties, default_properties)
```

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

```lua
local recompute = true

client.update_properties(custom_properties, default_properties, recompute)
```

### Identifying a session with a new ID

If you want to submit events to Satori before a user has authenticated with the game 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.

```lua
-- If the user's device ID is already stored, grab that
local uuid = require "nakama.util.uuid"

local player_prefs = sys.load("mygame", "player_prefs")

-- If the device identifier is invalid then let's generate a unique one.
if not player_prefs.device_id then
    player_prefs.device_id = uuid()
    -- Save the user's device ID so it can be retrieved during a later play session for re-authenticating.
    sys.save("mygame", "player_prefs", player_prefs)
end

-- Authenticate with Satori
local satori_session = client.authenticate(nil, nil, player_prefs.device_id)
if satori_session.token then
    print("Authenticated with Satori")
else
    print("Error authenticating")
end
```

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

```lua
local events_table = {
  satori.create_api_event(nil, nil, "gameFinished", time.now())
}
client.event(events_table)
```

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

```lua
local nakama_session = nakama.authenticate_email("example@heroiclabs.com", "password")
local user_id = nakama_session.user_id
```

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

```lua
local new_session = client.identify(custom_properties, default_properties, userId)
```

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:

```lua
client.delete_identity()
```

## Messages

Satori [integrates](/docs/satori/concepts/messages/#integrations) with various messaging services to send messages to users via [messages schedules](/docs/satori/concepts/messages/#message-schedules). You can list a user's messages, update a message's status, and delete a message.

### List messages

List all messages for the calling identity:

```lua
local limit = 10
local forward = true -- List messages oldest to newest

local messages = client.get_message_list(limit, forward)
```

### Update message status

Update the status of a message:

```lua
local id = "messageId"
local consume_time = "2021-01-01T00:00:00Z"
local read_time = "2021-01-01T00:00:00Z"

local message = satori.create_api_message(consume_time, nil, nil, read_time)
client.update_message(session, id, message)
```

### Delete message

Delete a scheduled message:

```lua
local id = "messageId"

client.delete_message(id)
```
