Client SDK
This feature is in beta. Core behavior is stable, but some APIs or configuration may change before general availability.
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 handlers.actions.
On the newer Room SDKs, the preferred public shape is a unified namespace surface with five core live capabilities:
room.stateroom.metaroom.membersroom.signalsroom.media(alpha)
Two companion namespaces round out the client runtime:
room.adminroom.session
Legacy flat methods such as room.send(...), room.getSharedState(), and room.onMessage(...) still exist for compatibility. Prefer the unified namespaces when your SDK supports them. See SDK Support.
The unified namespace (room.state.*, room.members.*, etc.) is currently available in the JavaScript/TypeScript SDK. Non-JS SDKs (Dart, Swift, Kotlin, Java, C#, C++) currently use the flat API surface shown in the compatibility tables below.
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: ^0.1.0
flutter pub get
// Package.swift
dependencies: [
.package(url: "https://github.com/melodysdreamj/edgebase-swift", from: "0.1.0")
]
Minimum: iOS 15+ / macOS 12+
// build.gradle.kts
dependencies {
implementation("dev.edgebase:edgebase-client-kotlin:0.1.0")
}
Works on Android, JVM, iOS (via Kotlin Multiplatform).
// build.gradle
dependencies {
implementation 'dev.edgebase:edgebase-android-java:0.1.0'
}
Unity: Copy packages/sdk/csharp/src/ into Assets/Plugins/EdgeBase/, or build a .dll and add it under Assets/Plugins/.
.NET: NuGet or project reference:
dotnet add package EdgeBase
FetchContent_Declare(
edgebase
GIT_REPOSITORY https://github.com/edgebase/edgebase-cpp.git
GIT_TAG v0.1.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.fun');
// ... 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.fun');
// ... authenticate first ...
final room = client.room('game', 'lobby-1');
await room.join();
To leave:
room.leave();
import EdgeBase
let client = EdgeBaseClient("https://your-project.edgebase.fun")
// ... authenticate first ...
let room = client.room(namespace: "game", id: "lobby-1")
try await room.join()
To leave:
room.leave()
import dev.edgebase.sdk.client.ClientEdgeBase
val client = ClientEdgeBase(url = "https://your-project.edgebase.fun")
// ... 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.fun");
// ... 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.fun");
// ... 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();
Unified Surface
The unified Room surface groups the product around the same live-session model used in the newer SDK implementations.
| Namespace | Purpose |
|---|---|
room.state | Authoritative commands plus shared / private state reads and subscriptions |
room.meta | Public-safe metadata before or after joining |
room.members | Presence list, member join/leave events, and ephemeral member state |
room.signals | Fire-and-forget room events and direct member sends |
room.media (alpha) | Audio/video/screen publish, mute, device, and track state |
room.admin | Moderation controls like kick, mute, disable video, and role changes |
room.session | Errors, reconnects, kicked events, and connection state |
const room = client.room('game', 'lobby-1');
await room.join();
const meta = await room.meta.get();
room.state.onSharedChange((state) => renderBoard(state));
room.members.onSync((members) => renderRoster(members));
room.signals.on('chat.message', (payload) => appendChat(payload));
room.media.onTrack((track, member) => attachTrack(track, member));
room.session.onConnectionStateChange((state) => console.log('room state:', state));
await room.state.send('MOVE', { to: { x: 5, y: 3 } });
await room.signals.send('chat.message', { text: 'hello' });
The rest of this page keeps the flat compatibility methods documented because they are still useful as cross-SDK fallbacks and map cleanly to the unified namespaces.
| Preferred namespace API | Compatibility API |
|---|---|
room.state.getShared() | room.getSharedState() |
room.state.getMine() | room.getPlayerState() |
room.state.onSharedChange(...) | room.onSharedState(...) |
room.state.onMineChange(...) | room.onPlayerState(...) |
room.state.send(...) | room.send(...) |
room.meta.get() | room.getMetadata() |
room.signals.on(...) / room.signals.onAny(...) | room.onMessage(...) / room.onAnyMessage(...) |
room.session.onError(...) / room.session.onKicked(...) | room.onError(...) / room.onKicked(...) |
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.state.getShared();
const player = room.state.getMine();
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.state.onSharedChange((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.state.onMineChange((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 handlers.actions entries and return the server's result.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
const result = await room.state.send('MOVE', { to: { x: 5, y: 3 } });
console.log('Move result:', result);
If the action fails server-side, the Promise rejects:
try {
await room.state.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;
}
);
Signals and Legacy Messages
On unified clients, prefer room.signals.on(...), room.signals.onAny(...), room.signals.send(...), and room.signals.sendTo(...).
The flat room.onMessage(...) and room.onAnyMessage(...) APIs below remain documented as compatibility wrappers for server-sent messages from room.sendMessage() or room.sendMessageTo() in server-side code.
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
// Listen for a specific signal event
const sub = room.signals.on('game_over', (data) => {
console.log('Winner:', data.winner);
});
sub.unsubscribe();
Listen for all messages regardless of type:
const sub = room.signals.onAny((event, data) => {
console.log(`Signal [${event}]:`, 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. On the unified surface, this lives under room.session.onKicked(...).
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
room.session.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
On the unified surface, error handling lives under room.session.onError(...).
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#/Unity
- C++/Unreal
room.session.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 replays the latest room state. On the unified surface, room.session.onReconnect(...) and room.session.onConnectionStateChange(...) are the primary lifecycle hooks.
- 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.
room.session.onReconnect(({ attempt }) => {
console.log('reconnecting attempt', attempt);
});
room.session.onConnectionStateChange((state) => {
console.log('connection state', state);
});
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.
Unified Surface Reference
The unified Room namespaces are currently implemented in the SDKs listed on SDK Support. Use this as the primary mental model when building new Room features.
| Namespace | Representative APIs |
|---|---|
room.state | getShared(), getMine(), onSharedChange(), onMineChange(), send() |
room.meta | get() |
room.members | list(), onSync(), onJoin(), onLeave(), setState(), clearState(), onStateChange() |
room.signals | send(), sendTo(), on(), onAny() |
room.media (alpha) | list(), audio.enable(), audio.setMuted(), video.enable(), screen.start(), devices.switch(), onTrack() |
room.admin | kick(), mute(), block(), setRole(), disableVideo(), stopScreenShare() |
room.session | onError(), onKicked(), onReconnect(), onConnectionStateChange() |
Compatibility API Reference
The tabbed tables below keep the flat compatibility methods that are still widely available across SDKs and older examples.
- 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 |