Satori Swift Client Guide #

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

Prerequisites #

Before proceeding ensure that you have:

The Satori client is packaged as part of the Nakama Swift 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 Heroic Labs GitHub Releases, as a Swift Package.

Swift Package Manager #

Add the following to your Package.swift:

1
2
3
4
5
6
let package = Package(
	// ...
	dependencies: [
    .package(url: "https://github.com/heroiclabs/nakama-swift.git", .upToNextMajor(from: "1.0.0"))
  ]
)

Or paste the git url in your Xcode project using Package Dependencies settings

Updates #

New versions of the Satori Swift Client and the corresponding improvements are documented in the Changelog.

Asynchronous programming #

Satori client uses swift concurrency async/await for it’s public methods.

You can call async methods using the await operator to not block the calling thread so that the app is responsive and efficient.

1
await client.authenticate("<deviceId>")

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 first import the satori package and then pass in your server connection details:

1
2
3
4
5
6
7
8
import Satori

let scheme = "http"
let host = "127.0.0.1"
let port: Int = 7450
let apiKey = "apiKey"

let client = HttpClient(scheme: scheme, host: host, port: port, apiKey: apiKey)
API Keys
Create and rotate API keys in the Satori Settings page.

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
10
func authenticateSatori() async {
    guard let deviceId = await UIDevice.current.identifierForVendor?.uuidString else { return }

    do {
        // Authenticate with the Satori server
        let session = try await client.authenticate(id: deviceId)
    } catch {
        debugPrint("Authentication failed")
    }
}

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 the tokens in UserDefaults or preferably more secure storage.

To restore a session without having to re-authenticate just pass the session which is about to expire to sessionRefresh method and you"ll receive a new one:

1
2
3
4
5
do {
    let session = try await client.sessionRefresh(session: expiringSession)
} catch {
    debugPrint("Error refreshing the session")
}

The Swift 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 in http client initializer:

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

Manually refreshing a session #

Sessions can be manually refreshed.

1
session = try await client.sessionRefresh(session: session)

Ending sessions #

Logout and end the current session:

1
try await client.authenticateLogout(session: session)

Experiments #

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

List the current experiments for this user:

1
let experiments = try await client.getAllExperiments(session: session)

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

1
2
let experiments = try await client.getExperiments(session: session, names: ["ExperimentOne", "ExperimentTwo"]
)

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
let flag = try await client.getFlag(session: session, name: "FlagName")

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

1
let flag = try await client.getFlag(session: session, name: "FlagName", defaultValue: "DefaultValue")

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
let flag = try await client.getFlagDefault(session: session, name: "FlagName")

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

1
var flag = try await client.getFlagDefault(session: session, name: "FlagName", defaultValue: "DefaultValue")

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
let flags = try await client.getFlags(session: session, names: [])

Listing default flags #

List all default feature flags:

1
let flags = try await client.getFlagsDefault(session: session)

Events #

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

Events can include metadata to be sent.

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

Sending single events #

1
try await client.event(session: session, event: Event(name: "gameFinished", timestamp: Date(), metadata: ["score":"1000"]))

Sending multiple events #

1
await client.events(session: session, events: [Event(name: "adStarted", timestamp: Date()), Event(name: "appLaunched", timestamp: Date())])

Live events #

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

List all available live events:

1
let liveEvents = try await client.getLiveEvents(session: session)

Identities #

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

List an identity’s properties #

1
var properties = try await client.listProperties(session: session)

Update an identity’s properties #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var defaultProperties = [
    "DefaultPropertyKey": "DefaultPropertyValue",
    "AnotherDefaultPropertyKey": "AnotherDefaultPropertyValue",
]

var customProperties = [
    "CustomPropertyKey": "CustomPropertyValue",
    "AnotherCustomPropertyKey": "AnotherCustomPropertyValue",
]

try await client.updateProperties(
    session: session,
    defaultProperties: defaultProperties,
    customProperties: customProperties
)

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

1
2
3
4
5
6
7
8
var recompute = true

try await client.updateProperties(
    session: session,
    defaultProperties: defaultProperties,
    customProperties: customProperties,
    recompute: recompute
)

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 a randomly generated one.

1
2
3
4
let tempId = "<temp_id>"

// Authenticate with Satori
var session = client.authenticate(tempId)

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

1
try await client.event(session: session, event: Event(name: "gameFinished", timestamp: Date()))

The user would then authenticate with the game backend and retrieve their User ID from the session:

1
2
var nakamaSession = try await nakamaClient.authenticateCustom("<nakamaCustomId>")
var nakamaUserId = nakamaSession.userId

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.

1
2
3
4
5
6
var newSession = try await client.identify(
    session: session,
    id: nakamaUserId,
    defaultProperties: [:],
    customProperties: [:]
)

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
try await client.deleteIdentity(session: session)