Client SDK
In v2 all state mutations happen server-side -- clients can only read state and send actions. The server mutates state in response to actions via the onAction handler.
Installation
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
npm install @edgebase/web
# React Native:
npm install @edgebase/react-native
# pubspec.yaml
dependencies:
edgebase_flutter: ^2.0.0
flutter pub get
// Package.swift
dependencies: [
.package(url: "https://github.com/edgebase/edgebase-swift", from: "2.0.0")
]
Minimum: iOS 13+ / macOS 10.15+
// build.gradle.kts
dependencies {
implementation("dev.edgebase:edgebase-kotlin:2.0.0")
}
Works on Android, JVM, iOS (via Kotlin Multiplatform).
// build.gradle
dependencies {
implementation 'dev.edgebase:edgebase-java:2.0.0'
}
Unity (UPM): Add via Package Manager or manifest.json:
{
"dependencies": {
"dev.edgebase.sdk": "https://github.com/edgebase/edgebase-unity.git"
}
}
.NET: NuGet:
dotnet add package EdgeBase
FetchContent_Declare(
edgebase
GIT_REPOSITORY https://github.com/edgebase/edgebase-cpp.git
GIT_TAG v2.0.0
)
FetchContent_MakeAvailable(edgebase)
target_link_libraries(your_target edgebase)
Requires: nlohmann/json header library.
Connect
Rooms are identified by a namespace and a room ID. Ensure the user is authenticated before connecting.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
import { createClient } from '@edgebase/web';
// React Native: import { createClient } from '@edgebase/react-native';
const client = createClient('https://your-project.edgebase.dev');
// ... authenticate first ...
const room = client.room('game', 'lobby-1');
await room.join();
To leave:
room.leave();
import 'package:edgebase_flutter/edgebase.dart';
final client = ClientEdgeBase('https://your-project.edgebase.dev');
// ... authenticate first ...
final room = client.room('game', 'lobby-1');
await room.join();
To leave:
room.leave();
import EdgeBaseCore
let client = EdgeBaseClient("https://your-project.edgebase.dev")
// ... authenticate first ...
let room = client.room("game", "lobby-1")
try await room.join()
To leave:
room.leave()
import dev.edgebase.sdk.client.ClientEdgeBase
val client = ClientEdgeBase(url = "https://your-project.edgebase.dev")
// ... authenticate first ...
val room = client.room("game", "lobby-1")
room.join() // suspend function
To leave:
room.leave()
import dev.edgebase.sdk.client.*;
ClientEdgeBase client = EdgeBase.client("https://your-project.edgebase.dev");
// ... authenticate first ...
RoomClient room = client.room("game", "lobby-1");
room.join().get(); // blocks until joined (or use .thenAccept() for async)
To leave:
room.leave();
using EdgeBase;
var client = new EdgeBase("https://your-project.edgebase.dev");
// ... authenticate first ...
var room = client.Room("game", "lobby-1");
await room.Join();
To leave:
room.Leave();
The C++ client requires injecting your own WebSocket transport before connecting:
#include "edgebase/room_client.h"
using json = nlohmann::json;
auto room = client.room("game", "lobby-1");
// Inject WebSocket functions (example with your WS library)
room->set_connect_fn([](const std::string& url,
std::function<void(const std::string&)> on_message,
std::function<void()> on_close) {
// Connect to url, call on_message for incoming data, on_close on disconnect
});
room->set_send_fn([](const std::string& message) {
// Send message over WebSocket
});
room->set_close_fn([]() {
// Close WebSocket connection
});
room->join();
To leave:
room->leave();
Read State
v2 has two separate state areas:
- Shared state -- visible to all players in the room.
- Player state -- private to each individual player.
Both are read-only on the client.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
const shared = room.getSharedState();
const player = room.getPlayerState();
final shared = room.getSharedState();
final player = room.getPlayerState();
let shared = room.getSharedState()
let player = room.getPlayerState()
val shared = room.getSharedState()
val player = room.getPlayerState()
Map<String, Object> shared = room.getSharedState();
Map<String, Object> player = room.getPlayerState();
var shared = room.GetSharedState();
var player = room.GetPlayerState();
json shared = room->get_shared_state();
json player = room->get_player_state();
Subscribe to State Changes
Shared State
Called on initial sync and whenever the shared state changes. The handler receives the full state and only the changed fields (delta).
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
const sub = room.onSharedState((state, changes) => {
console.log('Shared state:', state);
console.log('Changes:', changes);
renderGame(state);
});
// Later: stop listening
sub.unsubscribe();
final sub = room.onSharedState.listen((event) {
final state = event.state;
final changes = event.changes;
print('Shared state: $state');
setState(() => gameState = state);
});
// Later: stop listening
sub.cancel();
let sub = room.onSharedState { state, changes in
print("Shared state: \(state)")
print("Changes: \(changes)")
self.updateUI(state)
}
// Later: stop listening
sub.unsubscribe()
val sub = room.onSharedState { state, changes ->
println("Shared state: $state")
println("Changes: $changes")
updateUI(state)
}
// Later: stop listening
sub.unsubscribe()
Subscription sub = room.onSharedState((state, changes) -> {
System.out.println("Shared state: " + state);
System.out.println("Changes: " + changes);
updateUI(state);
});
// Later: stop listening
sub.unsubscribe();
var sub = room.OnSharedState((state, changes) =>
{
Debug.Log($"Shared state: {state}");
Debug.Log($"Changes: {changes}");
UpdateUI(state);
});
// Later: stop listening
sub.Dispose();
auto sub = room->on_shared_state([](const json& state, const json& changes) {
std::cout << "Shared state: " << state.dump() << std::endl;
std::cout << "Changes: " << changes.dump() << std::endl;
});
// Later: stop listening
sub.unsubscribe();
Player State
Called on initial sync and whenever the player's own state changes.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
const sub = room.onPlayerState((state, changes) => {
console.log('Player state:', state);
updateInventory(state);
});
sub.unsubscribe();
final sub = room.onPlayerState.listen((event) {
final state = event.state;
final changes = event.changes;
print('Player state: $state');
updateInventory(state);
});
sub.cancel();
let sub = room.onPlayerState { state, changes in
print("Player state: \(state)")
updateInventory(state)
}
sub.unsubscribe()
val sub = room.onPlayerState { state, changes ->
println("Player state: $state")
updateInventory(state)
}
sub.unsubscribe()
Subscription sub = room.onPlayerState((state, changes) -> {
System.out.println("Player state: " + state);
updateInventory(state);
});
sub.unsubscribe();
var sub = room.OnPlayerState((state, changes) =>
{
Debug.Log($"Player state: {state}");
UpdateInventory(state);
});
sub.Dispose();
auto sub = room->on_player_state([](const json& state, const json& changes) {
std::cout << "Player state: " << state.dump() << std::endl;
});
sub.unsubscribe();
Send Actions
Actions are sent to the server's onAction handler and return the server's result.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
const result = await room.send('MOVE', { to: { x: 5, y: 3 } });
console.log('Move result:', result);
If the action fails server-side, the Promise rejects:
try {
await room.send('ATTACK', { target: 'player-2' });
} catch (err) {
console.error('Action failed:', err.message);
}
final result = await room.send('MOVE', {'to': {'x': 5, 'y': 3}});
print('Move result: $result');
If the action fails server-side, the Future throws:
try {
await room.send('ATTACK', {'target': 'player-2'});
} catch (e) {
print('Action failed: $e');
}
let result = try await room.send("MOVE", payload: ["to": ["x": 5, "y": 3]])
print("Move result: \(result)")
If the action fails server-side, it throws:
do {
try await room.send("ATTACK", payload: ["target": "player-2"])
} catch {
print("Action failed: \(error)")
}
val result = room.send("MOVE", mapOf("to" to mapOf("x" to 5, "y" to 3)))
println("Move result: $result")
If the action fails server-side, the suspend function throws:
try {
room.send("ATTACK", mapOf("target" to "player-2"))
} catch (e: EdgeBaseException) {
println("Action failed: ${e.message}")
}
CompletableFuture<Object> future = room.send("MOVE", Map.of("to", Map.of("x", 5, "y", 3)));
Object result = future.get(); // blocks for result
System.out.println("Move result: " + result);
Async style:
room.send("MOVE", Map.of("to", Map.of("x", 5, "y", 3)))
.thenAccept(result -> System.out.println("Move result: " + result))
.exceptionally(err -> {
System.err.println("Action failed: " + err.getMessage());
return null;
});
var result = await room.Send("MOVE", new { to = new { x = 5, y = 3 } });
Debug.Log($"Move result: {result}");
If the action fails server-side, the Task throws:
try
{
await room.Send("ATTACK", new { target = "player-2" });
}
catch (EdgeBaseException ex)
{
Debug.LogError($"Action failed: {ex.Message}");
}
room->send("MOVE", {{"to", {{"x", 5}, {"y", 3}}}},
[](const json& result) {
std::cout << "Move result: " << result.dump() << std::endl;
},
[](const std::string& error) {
std::cerr << "Action failed: " << error << std::endl;
}
);
Messages
Listen for server-sent messages (sent by room.sendMessage() or room.sendMessageTo() in server-side code).
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
// Listen for a specific message type
const sub = room.onMessage('game_over', (data) => {
console.log('Winner:', data.winner);
});
sub.unsubscribe();
Listen for all messages regardless of type:
const sub = room.onAnyMessage((messageType, data) => {
console.log(`Message [${messageType}]:`, data);
});
sub.unsubscribe();
final sub = room.onMessage('game_over').listen((data) {
print('Winner: ${data['winner']}');
});
sub.cancel();
let sub = room.onMessage("game_over") { data in
if let winner = (data as? [String: Any])?["winner"] as? String {
print("Winner: \(winner)")
}
}
sub.unsubscribe()
val sub = room.onMessage("game_over") { data ->
val winner = (data as? Map<*, *>)?.get("winner") as? String
println("Winner: $winner")
}
sub.unsubscribe()
Subscription sub = room.onMessage("game_over", data -> {
Map<String, Object> map = (Map<String, Object>) data;
System.out.println("Winner: " + map.get("winner"));
});
sub.unsubscribe();
var sub = room.OnMessage("game_over", (data) =>
{
var dict = data as Dictionary<string, object>;
Debug.Log($"Winner: {dict?["winner"]}");
});
sub.Dispose();
auto sub = room->on_message("game_over", [](const json& data) {
std::string winner = data.value("winner", "");
std::cout << "Winner: " << winner << std::endl;
});
sub.unsubscribe();
-> Server-side message sending: Server Guide
Kicked
The server can kick a player. After being kicked, auto-reconnect is disabled.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
room.onKicked(() => {
console.log('You were kicked from the room');
showKickedUI();
});
room.onKicked.listen((_) {
print('You were kicked from the room');
showKickedUI();
});
room.onKicked {
print("You were kicked from the room")
showKickedUI()
}
room.onKicked {
println("You were kicked from the room")
showKickedUI()
}
room.onKicked(() -> {
System.out.println("You were kicked from the room");
showKickedUI();
});
room.OnKicked(() =>
{
Debug.Log("You were kicked from the room");
ShowKickedUI();
});
room->on_kicked([]() {
std::cout << "You were kicked from the room" << std::endl;
show_kicked_ui();
});
-> Server-side kick setup: Server Guide
Error Handling
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
room.onError((err) => {
console.error(`Room error [${err.code}]: ${err.message}`);
});
room.onError.listen((err) {
print('Room error [${err.code}]: ${err.message}');
});
room.onError { err in
print("Room error [\(err.code)]: \(err.message)")
}
room.onError { err ->
println("Room error [${err.code}]: ${err.message}")
}
room.onError(err -> {
System.err.println("Room error [" + err.getCode() + "]: " + err.getMessage());
});
room.OnError((err) =>
{
Debug.LogError($"Room error [{err.Code}]: {err.Message}");
});
room->on_error([](const std::string& code, const std::string& message) {
std::cerr << "Room error [" << code << "]: " << message << std::endl;
});
Auto-Reconnect
Built-in with exponential backoff. If the WebSocket drops, the SDK automatically reconnects, re-authenticates, and triggers onSharedState / onPlayerState with the latest state. No application code needed.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
Configure via options:
const room = client.room('game', 'lobby-1', {
autoReconnect: true, // default: true
maxReconnectAttempts: 10, // default: 10
reconnectBaseDelay: 1000, // default: 1000ms
sendTimeout: 10000, // default: 10000ms
});
React Native: Works transparently across app foreground/background transitions.
Automatic. No configuration needed.
Uses URLSessionWebSocketTask. No configuration needed.
Uses Ktor WebSocket client. No configuration needed.
Uses ScheduledExecutorService. No configuration needed.
Automatic. No configuration needed.
On WebGL, the SDK uses a JavaScript interop bridge (jslib) for WebSocket since System.Net.WebSockets.ClientWebSocket is not available. The API is identical.
Uses an internal std::thread. No configuration needed.
API Reference
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
| Method / Property | Type | Description |
|---|---|---|
client.room(namespace, roomId, options?) | RoomClient | Create a room client |
room.join() | Promise<void> | Connect, authenticate, and join |
room.leave() | void | Disconnect and clean up |
room.getSharedState() | Record<string, unknown> | Get current shared state snapshot |
room.getPlayerState() | Record<string, unknown> | Get current player state snapshot |
room.send(actionType, payload?) | Promise<unknown> | Send action to server, returns result |
room.onSharedState(handler) | Subscription | Shared state changes -- returns { unsubscribe() } |
room.onPlayerState(handler) | Subscription | Player state changes -- returns { unsubscribe() } |
room.onMessage(type, handler) | Subscription | Server message by type -- returns { unsubscribe() } |
room.onAnyMessage(handler) | Subscription | All server messages -- returns { unsubscribe() } |
room.onError(handler) | Subscription | Error occurred -- returns { unsubscribe() } |
room.onKicked(handler) | Subscription | Kicked from room -- returns { unsubscribe() } |
room.namespace | string | Room namespace |
room.roomId | string | Room instance ID |
| Method / Property | Type | Description |
|---|---|---|
client.room(namespace, roomId) | RoomClient | Create a room client |
room.join() | Future<void> | Connect, authenticate, and join |
room.leave() | void | Disconnect and clean up |
room.getSharedState() | Map<String, dynamic> | Get current shared state snapshot |
room.getPlayerState() | Map<String, dynamic> | Get current player state snapshot |
room.send(actionType, [payload]) | Future<dynamic> | Send action to server, returns result |
room.onSharedState | Stream<StateEvent> | Shared state changes |
room.onPlayerState | Stream<StateEvent> | Player state changes |
room.onMessage(type) | Stream<dynamic> | Server message by type |
room.onError | Stream<RoomError> | Error occurred |
room.onKicked | Stream<void> | Kicked from room |
room.namespace | String | Room namespace |
room.roomId | String | Room instance ID |
| Method / Property | Type | Description |
|---|---|---|
client.room(namespace, roomId) | RoomClient | Create a room client |
room.join() | async throws | Connect, authenticate, and join |
room.leave() | void | Disconnect and clean up |
room.getSharedState() | [String: Any] | Get current shared state snapshot |
room.getPlayerState() | [String: Any] | Get current player state snapshot |
room.send(_:payload:) | async throws -> Any | Send action to server, returns result |
room.onSharedState(_:) | Subscription | Shared state changes |
room.onPlayerState(_:) | Subscription | Player state changes |
room.onMessage(_:handler:) | Subscription | Server message by type |
room.onError(_:) | Subscription | Error occurred |
room.onKicked(_:) | Subscription | Kicked from room |
room.namespace | String | Room namespace |
room.roomId | String | Room instance ID |
| Method / Property | Type | Description |
|---|---|---|
client.room(namespace, roomId) | RoomClient | Create a room client |
room.join() | suspend | Connect, authenticate, and join |
room.leave() | Unit | Disconnect and clean up |
room.getSharedState() | Map<String, Any?> | Get current shared state snapshot |
room.getPlayerState() | Map<String, Any?> | Get current player state snapshot |
room.send(actionType, payload?) | suspend -> Any? | Send action to server, returns result |
room.onSharedState(handler) | Subscription | Shared state changes |
room.onPlayerState(handler) | Subscription | Player state changes |
room.onMessage(type, handler) | Subscription | Server message by type |
room.onError(handler) | Subscription | Error occurred |
room.onKicked(handler) | Subscription | Kicked from room |
room.namespace | String | Room namespace |
room.roomId | String | Room instance ID |
| Method / Property | Type | Description |
|---|---|---|
client.room(namespace, roomId) | RoomClient | Create a room client |
room.join() | CompletableFuture<Void> | Connect, authenticate, and join |
room.leave() | void | Disconnect and clean up |
room.getSharedState() | Map<String, Object> | Get current shared state snapshot |
room.getPlayerState() | Map<String, Object> | Get current player state snapshot |
room.send(actionType, payload) | CompletableFuture<Object> | Send action to server, returns result |
room.onSharedState(handler) | Subscription | Shared state changes |
room.onPlayerState(handler) | Subscription | Player state changes |
room.onMessage(type, handler) | Subscription | Server message by type |
room.onError(handler) | Subscription | Error occurred |
room.onKicked(handler) | Subscription | Kicked from room |
room.getNamespace() | String | Room namespace |
room.getRoomId() | String | Room instance ID |
| Method / Property | Type | Description |
|---|---|---|
client.Room(namespace, roomId) | RoomClient | Create a room client |
room.Join() | Task | Connect, authenticate, and join |
room.Leave() | void | Disconnect and clean up |
room.GetSharedState() | Dictionary<string, object?> | Get current shared state snapshot |
room.GetPlayerState() | Dictionary<string, object?> | Get current player state snapshot |
room.Send(actionType, payload?) | Task<object> | Send action to server, returns result |
room.OnSharedState(handler) | IDisposable | Shared state changes |
room.OnPlayerState(handler) | IDisposable | Player state changes |
room.OnMessage(type, handler) | IDisposable | Server message by type |
room.OnError(handler) | IDisposable | Error occurred |
room.OnKicked(handler) | IDisposable | Kicked from room |
room.Namespace | string | Room namespace |
room.RoomId | string | Room instance ID |
| Method / Property | Type | Description |
|---|---|---|
client.room(namespace, room_id) | shared_ptr<RoomClient> | Create a room client |
room->join() | void | Connect, authenticate, and join |
room->leave() | void | Disconnect and clean up |
room->get_shared_state() | json | Get current shared state snapshot |
room->get_player_state() | json | Get current player state snapshot |
room->send(type, payload, on_result, on_error) | void | Send action to server (callback-based) |
room->on_shared_state(handler) | Subscription | Shared state changes |
room->on_player_state(handler) | Subscription | Player state changes |
room->on_message(type, handler) | Subscription | Server message by type |
room->on_error(handler) | Subscription | Error occurred |
room->on_kicked(handler) | Subscription | Kicked from room |
room->namespace_id | std::string | Room namespace |
room->room_id | std::string | Room instance ID |
room->set_connect_fn(fn) | void | Inject WebSocket connect |
room->set_send_fn(fn) | void | Inject WebSocket send |
room->set_close_fn(fn) | void | Inject WebSocket close |