https://github.com/heroiclabs/nakama-defold
Defold client for Nakama server.
https://github.com/heroiclabs/nakama-defold
chat defold defold-module lua multiplayer nakama-server
Last synced: 3 months ago
JSON representation
Defold client for Nakama server.
- Host: GitHub
- URL: https://github.com/heroiclabs/nakama-defold
- Owner: heroiclabs
- License: apache-2.0
- Created: 2020-03-27T15:56:20.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2024-09-16T11:26:41.000Z (9 months ago)
- Last Synced: 2025-03-15T08:08:46.906Z (3 months ago)
- Topics: chat, defold, defold-module, lua, multiplayer, nakama-server
- Language: Lua
- Homepage: https://heroiclabs.com
- Size: 188 KB
- Stars: 80
- Watchers: 14
- Forks: 13
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-defold - Nakama
README

# Nakama
> Lua client for Nakama server written in Lua 5.1.
[Nakama](https://github.com/heroiclabs/nakama) is an open-source server designed to power modern games and apps. Features include user accounts, chat, social, matchmaker, realtime multiplayer, and much [more](https://heroiclabs.com).
This client implements the full API and socket options with the server. It's written in Lua 5.1 to be compatible with Lua based game engines.
Full documentation is available [here](https://heroiclabs.com/docs/nakama/client-libraries/defold/index.html).
## Getting Started
You'll need to setup the server and database before you can connect with the client. The simplest way is to use Docker but have a look at the [server documentation](https://github.com/heroiclabs/nakama#getting-started) for other options.
1. Install and run the servers. Follow these [instructions](https://heroiclabs.com/docs/nakama/getting-started/install/docker/).
2. Add the client to your project.
* In Defold projects you need to add the URL of a [stable release](https://github.com/heroiclabs/nakama-defold/releases) or the [latest development version](https://github.com/heroiclabs/nakama-defold/archive/master.zip) as a library dependency to `game.project`. The client will now show up in `nakama` folder in your project.
3. Add dependencies to your project. In Defold projects you need to add one of the following dependencies to game.project:
* https://github.com/defold/extension-websocket/archive/2.1.0.zip (Defold version <= 1.2.181)
* https://github.com/defold/extension-websocket/archive/3.0.0.zip (Defold version >= 1.2.182)4. Use the connection credentials to initialise the nakama client.
```lua
local defold = require "nakama.engine.defold"
local nakama = require "nakama.nakama"
local config = {
host = "127.0.0.1",
port = 7350,
use_ssl = false,
username = "defaultkey",
password = "",
engine = defold,
timeout = 10, -- connection timeout in seconds
}
local client = nakama.create_client(config)
```5. (Optional) Nakama uses base64 decoding for session the session tokens and both base64 encoding and decoding of match data. The default base64 encoder and decoder is written in Lua. To increase performance of the base64 encode and decode steps it is possible to use a base64 encoder written in C. In Defold projects you need to add the following dependency to game.project:
* https://github.com/defold/extension-crypt/archive/refs/tags/1.0.2.zip
## Usage
The client has many methods to execute various features in the server or open realtime socket connections with the server.
### Authenticate
There's a variety of ways to [authenticate](https://heroiclabs.com/docs/authentication) with the server. Authentication can create a user if they don't already exist with those credentials. It's also easy to authenticate with a social profile from Google Play Games, Facebook, Game Center, etc.
```lua
local client = nakama.create_client(config)local email = "[email protected]"
local password = "batsignal"
local session = client.authenticate_email(email, password)
pprint(session)
```> _Note_: see [Requests](#Requests) section below for running this snippet (a)synchronously.
### Sessions
When authenticated the server responds with an auth token (JWT) which can be used to authenticate API requests. The token contains useful properties and gets deserialized into a `session` table.
```lua
local client = nakama.create_client(config)local session = client.authenticate_email(email, password)
print(session.created)
print(session.token) -- raw JWT token
print(session.expires)
print(session.user_id)
print(session.username)
print(session.refresh_token) -- raw JWT token for use when refreshing the session
print(session.refresh_token_expires)
print(session.refresh_token_user_id)
print(session.refresh_token_username)-- Use the token to authenticate future API requests
nakama.set_bearer_token(client, session.token)-- Use the refresh token to refresh the authentication token
nakama.session_refresh(client, session.refresh_token)
```It is recommended to store the auth token from the session and check at startup if it has expired. If the token has expired you must reauthenticate. If the token is about to expire it has to be refreshed. The expiry time of the token can be changed as a setting in the server. You can store the session using `session.store(session)` and later restored it using `session.restore()`:
```lua
local nakama_session = require "nakama.session"local client = nakama.create_client(config)
-- restore a session
local session = nakama_session.restore()if session and nakama_session.is_token_expired_soon(session) and not nakama.is_refresh_token_expired(session) then
print("Session has expired or is about to expire. Refreshing.")
session = nakama.session_refresh(client, session.refresh_token)
nakama_session.store(session)
elseif not session or nakama_session.is_refresh_token_expired(session) then
print("Session does not exist or it has expired. Must reauthenticate.")
session = client.authenticate_email("[email protected]", "foobar123", nil, true, "britzl")
nakama_session.store(session)
end
client.set_bearer_token(session.token)
```### Requests
The client includes lots of built-in APIs for various features of the game server. These can be accessed with the methods which either use a callback function to return a result (ie. asynchronous) or yield until a result is received (ie. synchronous and must be run within a Lua coroutine).
```lua
local client = nakama.create_client(config)-- using a callback
client.get_account(function(account)
print(account.user.id);
print(account.user.username);
print(account.wallet);
end)-- if run from within a coroutine
local account = client.get_account()
print(account.user.id);
print(account.user.username);
print(account.wallet);
```The Nakama client provides a convenience function for creating and starting a coroutine to run multiple requests synchronously one after the other:
```lua
nakama.sync(function()
local account = client.get_account()
local result = client.update_account(request)
end)
```### Retries
Nakama has a global and per-request retry configuration to control how failed API calls are retried.```lua
local retries = require "nakama.util.retries"-- use a global retry policy with 5 attempts with 1 second intervals
local config = {
host = "127.0.0.1",
port = 7350,
username = "defaultkey",
password = "",
retry_policy = retries.fixed(5, 1),
engine = defold,
}
local client = nakama.create_client(config)-- use a retry policy specifically for this request
-- 5 retries at intervals increasing by 1 second between attempts (eg 1s, 2s, 3s, 4s, 5s)
nakama.list_friends(client, 10, 0, "", retries.incremental(5, 1))
```### Cancelling requests
Create a cancellation token and pass that with a request to cancel the request before it has completed.```lua
-- use a global retry policy with 5 attempts with 1 second intervals
local config = {
host = "127.0.0.1",
port = 7350,
username = "defaultkey",
password = "",
retry_policy = retries.fixed(5, 1),
engine = defold,
}
local client = nakama.create_client(config)-- create a cancellation token
local token = nakama.cancellation_token()-- start a request and proivide the cancellation token
nakama.list_friends(client, 10, 0, "", nil, callback, token)-- immediately cancel the request without waiting for the request callback to be invoked
nakama.cancel(token)
```### Socket
You can connect to the server over a realtime WebSocket connection to send and receive chat messages, get notifications, and matchmake into a multiplayer match.
You first need to create a realtime socket to the server:
```lua
local client = nakama.create_client(config)-- create socket
local socket = client.create_socket()nakama.sync(function()
-- connect
local ok, err = socket.connect()
end)
```Then proceed to join a chat channel and send a message:
```lua
-- send channel join message
local channel_id = "pineapple-pizza-lovers-room"
local result = socket.channel_join(socket, 1, channel_id, false, false)-- send channel messages
local result = socket.channel_message_send(channel_id, "Pineapple doesn't belong on a pizza!")
```#### Handle events
A client socket has event listeners which are called on various events received from the server. Example:
```lua
socket.on_disconnect(function(message)
print("Disconnected!")
end)
```Available listeners:
* `on_disconnect` - Handles an event for when the client is disconnected from the server.
* `on_channel_presence_event`
* `on_match_presence_event`
* `on_match_data`
* `on_match`
* `on_matchmaker_matched`
* `on_notifications`
* `on_party_presence_event`
* `on_party`
* `on_party_data`
* `on_party_join_request`
* `on_status_presence_event`
* `on_status`
* `on_stream_data`
* `on_error`
* `on_channel_message`
* `on_channel_message`### Match data
Nakama [supports any binary content](https://heroiclabs.com/docs/gameplay-multiplayer-realtime/#send-data-messages) in `data` attribute of a match message. Regardless of your data type, the server **only accepts base64-encoded data**, so make sure you don't post plain-text data or even JSON, or Nakama server will claim the data malformed and disconnect your client (set server logging to `debug` to detect these events).
Nakama will automatically base64 encode your match data if the message was created using `nakama.create_match_data_message()`. Nakama will also automatically base64 decode any received match data before calling the `on_matchdata` listener.
```lua
local json = require "nakama.util.json"
local match_id = "..."
local op_code = 1
local data = json.encode({
dest_x = 1.0,
dest_y = 0.1,
})-- send a match data message. The data will be automatically base64 encoded.
socket.match_data(match_id, op_code, data)
```In a relayed multiplayer, you'll be receiving other clients' messages. The client has already base64 decoded the message data before sending it to the `on_matchdata` listener. If the data was JSON encoded, like in the example above, you need to decode it yourself:
```lua
socket.on_matchdata(function(message)
local match_data = message.match_data
local data = json.decode(match_data.data)
pprint(data) -- gameplay coordinates from the example above
end)
```Messages initiated _by the server_ in an authoritative match will come as valid JSON by default.
# Satori
> Lua client for Satori written in Lua 5.1.
[Satori](https://heroiclabs.com/satori/) is a liveops server for games that powers actionable analytics, A/B testing and remote configuration. Use the Satori Defold client to communicate with Satori from within your Defold game.
## Getting started
Create a Satori client using the API key from the Satori dashboard.
```lua
local config = {
host = "myhost.com",
api_key = "my-api-key",
use_ssl = true,
port = 443,
retry_policy = retries.incremental(5, 1),
engine = defold,
}
local client = satori.create_client(config)
```Then authenticate to obtain your session:
```lua
satori.sync(function()
local uuid = defold.uuid()
local result = client.authenticate(nil, nil, uuid)
if not result.token then
error("Unable to login")
return
end
client.set_bearer_token(result.token)
end)
```Using the client you can get any experiments or feature flags, the user belongs to.
```lua
satori.sync(function()
local experiments = satori.get_experiments(client)
pprint(experiments)local flags = satori.get_flags(client)
pprint(flags)
end)
```# Contribute
The development roadmap is managed as GitHub issues and pull requests are welcome. If you're interested to enhance the code please open an issue to discuss the changes or drop in and discuss it in the [community forum](https://forum.heroiclabs.com).
## Run tests
Unit tests can be found in the `tests` folder. Run them using [Telescope](https://github.com/defold/telescope) (fork which supports Lua 5.3+):
```
./tsc -f test/test_nakama.lua test/test_satori.lua test/test_socket.lua test/test_session.lua
```## Generate Docs
API docs are generated with Ldoc and deployed to GitHub pages.
When changing the API comments, rerun Ldoc and commit the changes in `docs/*`.
Note: Comments for `nakama/nakama.lua` must be made in `codegen/main.go`.
To run Ldoc:
```
# in the project root, generate nakama.lua
# requires go and https://github.com/heroiclabs/nakama to be checked out
go run codegen/main.go -output nakama/nakama.lua ../nakama/apigrpc/apigrpc.swagger.json# install ldoc (mac)
brew install luarocks
luarocks install ldoc# run ldoc
doc . -d docs
```## Generate code
Refer to instructions in the [codegen folder](/codegen).
## Adapting to other engines
Adapting the Nakama and Satori Defold clients to another Lua based engine should be as easy as providing another engine module when configuring the Nakama client:
```lua
-- nakama
local myengine = require "nakama.engine.myengine"
local nakama = require "nakama.nakama"
local nakama_config = {
engine = myengine,
}
local nakama_client = nakama.create_client(nakama_config)-- satori
local myengine = require "nakama.engine.myengine"
local satori = require "satori.satori"
local satori_config = {
engine = myengine,
}
local satori_client = satori.create_client(satori_config)
```The engine module must provide the following functions:
* `http(config, url_path, query_params, method, post_data, cancellation_token, callback)` - Make HTTP request.
* `config` - Config table passed to `nakama.create()` or `satori.create()`
* `url_path` - Path to append to the base uri
* `query_params` - Key-value pairs to use as URL query parameters
* `method` - "GET", "POST"
* `post_data` - Data to post
* `cancellation_token` - Check if `cancellation_token.cancelled` is true
* `callback` - Function to call with result (response)* `socket_create(config, on_message)` - Create socket. Must return socket instance (table with engine specific socket state).
* `config` - Config table passed to `nakama.create()` or `satori.create()
* `on_message` - Function to call when a message is sent from the server* `socket_connect(socket, callback)` - Connect socket.
* `socket` - Socket instance returned from `socket_create()`
* `callback` - Function to call with result (ok, err)* `socket_send(socket, message)` - Send message on socket.
* `socket` - Socket instance returned from `socket_create()`
* `message` - Message to send
* `uuid()` - Create a UUID# Licenses
This project is licensed under the [Apache-2 License](https://github.com/heroiclabs/nakama-defold/blob/master/LICENSE).