Step 3 - Connect, and prove it talks
Goal: open the realtime socket and confirm the server accepted it, and nothing else.
This is the confidence anchor. Before you register anything, match anyone, or move your fighter, you want one fact on your screen: the socket is up and the server knows who you are. That is the whole job of this step.
The handshake
The realtime channel is one WebSocket. The first frame your client sends is session.connect carrying your access token. The server replies with session.connected carrying your player_id. That round trip is the proof. See the WebSocket protocol guide for the full frame surface.
Every SDK does this handshake for you inside its connect call. Your job is to register a handler for session.connected first, then call connect, then read the player_id off the confirmation.
You send intent, the server decides. Here the intent is "let me in", and session.connected is the server deciding yes.
One thing differs: the base URL
The only difference between managed cloud and self-hosting on this step is the URL you point the client at. The calls below are identical either way.
- Cloud: use your environment's URL from the console at
console.asobi.dev(awss://address the platform issued when you deployed). The token comes from signing in against that same environment. - Self-hosted: your own release listens on port
8084at/ws, sows://<your-host>:8084/ws(orwss://behind TLS). Default port and endpoint are in the configuration guide.
Point the SDK at the right base URL and the rest of this page is the same on both.
You need a token first
Connect authenticates the socket, so the SDK must already be holding an access token. Do any auth call before connecting; the SDK stores the token on the client for you. For now a throwaway login or register is enough to get moving. Step 4 (Who is the player?) covers guest versus account properly, so do not overthink it here.
Per-SDK
Register the session.connected handler before you call connect, in every SDK.
Defold. connect() authenticates asynchronously, so wait for the connected callback before doing anything else (README example). Install is the SDK zip plus the extension-websocket dependency in game.project (README, Installation).
Godot. The connected signal fires with no arguments, so read the id from the auth response (var resp := await Asobi.auth.login(...) in the README example) rather than from the signal. Log in before connect_to_server().
Unity. OnConnected is an Action with no arguments; the confirmed id lives on client.PlayerId after authentication (AsobiClient.cs). Authenticate the client before ConnectAsync(). Realtime events fire on a background thread, so marshal to the main thread before touching UnityEngine.Object (README threading note).
Unreal. Note the ordering caveat: OnConnected fires when the transport opens, which is before the auth handshake, so it is socket-open, not session-ready. There is no typed delegate for the post-auth session.connected reply in this SDK: only the transport-level OnConnected (which fires pre-auth) and the raw OnMessage catch-all. Call Authenticate(Token) from the OnConnected handler, and read the confirmed id from Client->GetPlayerId() (AsobiClient.h) once the account is known.
Dart/Flame. onConnected is a Stream<void>, so the event carries no value; read the id from client.playerId, which the SDK sets at auth (asobi_client.dart). The README example completes a Completer on this stream to await the connection.
JavaScript. This SDK uses the raw wire event name (session.connected, with the fighter) and hands you the untyped payload, so payload.player_id is right there (README; websocket.ts). There is no client.realtime; create the socket with asobi.websocket({ token }).
LOVE. The connected callback receives the payload, so payload.player_id works; the SDK also stores it on client.realtime.local_player_id (realtime.lua). LOVE runs one cooperative loop, so you must call client.realtime:update() every frame from love.update(dt) or no callbacks fire, including connected (README). Auth calls are synchronous and block the frame, so run them at startup.
local rt = client.realtime
rt:on("connected", function(payload)
print("connected, player_id=" .. payload.player_id)
end)
rt:connect()Asobi.realtime.connected.connect(_on_connected)
Asobi.realtime.connect_to_server()
func _on_connected() -> void:
print("connected, player_id=%s" % resp["player_id"])client.Realtime.OnConnected += () =>
Debug.Log($"connected, player_id={client.PlayerId}");
await client.Realtime.ConnectAsync();WebSocket->OnConnected.AddDynamic(this, &UMyClass::HandleConnected);
WebSocket->Connect(Url);
void UMyClass::HandleConnected()
{
WebSocket->Authenticate(Token);
UE_LOG(LogTemp, Log, TEXT("socket open, player_id=%s"), *Client->GetPlayerId());
}client.realtime.onConnected.stream.listen((_) {
print('connected, player_id=${client.playerId}');
});
await client.realtime.connect(autoReconnect: false);const ws = asobi.websocket({ token });
ws.on("session.connected", (payload) => {
console.log("connected, player_id=" + payload.player_id);
});
await ws.connect();client.realtime:on("connected", function(payload)
print("connected, player_id=" .. payload.player_id)
end)
assert(client.realtime:connect())Checkpoint
Run your client. You should see exactly one line, something like:
connected, player_id=0192f3a1-7c4e-7a1b-9d2e-6f0b8c3a11ff
If that line prints, the socket is open and the server has accepted your session. That is the entire step. If it does not print, check three things in order: the base URL matches your environment (cloud console URL versus self-hosted :8084/ws), you made an auth call before connecting, and you registered the connected handler before calling connect.
Next: Step 4 - Who is the player? Guest versus account.
You logged in with a throwaway credential to get here; next you decide whether the player needs to register at all.