docs: major corrections to ecast API docs from second round of testing
Key corrections based on testing with fresh room SCWX: - connections count includes ALL ever-joined players, not just active ones (slots persist for reconnection, count never decreases) - here field also includes disconnected players (slot reservation model) - client/connected and client/disconnected confirmed as NOT delivered to player connections after extensive testing - Jackbox has no concept of "leaving" — player disconnect is invisible to the API - Added reconnection URL format (secret + id query params) - Added error code 2027 (REST/WebSocket state divergence) - Added ws-lifecycle-test.js for systematic protocol testing Made-with: Cursor
This commit is contained in:
@@ -158,11 +158,11 @@ Room information with audience data and join role. Uses a different response for
|
||||
|
||||
### GET /api/v2/rooms/{code}/connections
|
||||
|
||||
Active connection count. Includes all connection types (host, players, audience, shards).
|
||||
Total allocated slot count. Includes all connection types (host, players, audience, shards).
|
||||
|
||||
**Response:** `{"ok": true, "body": {"connections": 3}}`
|
||||
|
||||
> **Important:** This is the most reliable REST-based way to get a real-time count of connected entities. Subtract 1 for the host connection. Note that ungracefully disconnected clients may linger for up to 30-60 seconds before the server detects the dead connection.
|
||||
> **Important:** This counts allocated **slots**, not currently-active WebSocket connections. Jackbox holds player slots open indefinitely for reconnection — there is no concept of "leaving" a game. A player who closes their browser tab still occupies a slot. The count only decreases if the room itself is destroyed. Therefore, `connections - 1` gives the number of players who have **ever joined** the room, not the number currently online.
|
||||
|
||||
### POST /api/v2/rooms
|
||||
|
||||
@@ -235,7 +235,7 @@ Only `/api/v2/` is active. Requests to `/api/v1/`, `/api/v3/`, or `/api/v4/` ret
|
||||
|
||||
### Connection
|
||||
|
||||
**Player connection URL:**
|
||||
**Player connection URL (new join):**
|
||||
|
||||
```
|
||||
wss://{host}/api/v2/rooms/{code}/play?role=player&name={name}&userId={userId}&format=json
|
||||
@@ -248,6 +248,21 @@ wss://{host}/api/v2/rooms/{code}/play?role=player&name={name}&userId={userId}&fo
|
||||
| `userId` | Yes | Unique user identifier |
|
||||
| `format` | Yes | `json` (only known format) |
|
||||
|
||||
**Player reconnection URL (existing session):**
|
||||
|
||||
```
|
||||
wss://{host}/api/v2/rooms/{code}/play?role=player&name={name}&format=json&secret={secret}&id={id}
|
||||
```
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `secret` | Yes | Session secret from original `client/welcome` response |
|
||||
| `id` | Yes | Session ID from original `client/welcome` response |
|
||||
| `name` | Yes | Display name (can differ from original) |
|
||||
| `format` | Yes | `json` |
|
||||
|
||||
On reconnection, the server returns `client/welcome` with `reconnect: true` and the same `id` and `secret`. The player resumes their existing slot rather than consuming a new one.
|
||||
|
||||
**Audience connection URL:**
|
||||
|
||||
```
|
||||
@@ -316,13 +331,13 @@ const ws = new WebSocket(url, ['ecast-v0'], {
|
||||
| Opcode | Direction | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `client/welcome` | S→C | Sent immediately after connection. Contains initial state, entities, connected users. |
|
||||
| `client/connected` | S→C | Notification that another client connected. |
|
||||
| `client/disconnected` | S→C | Notification that another client disconnected. |
|
||||
| `client/connected` | S→C | Notification that another client connected (see note below). |
|
||||
| `client/disconnected` | S→C | Notification that another client disconnected (see note below). |
|
||||
| `client/kicked` | S→C | Notification that you were kicked from the room. |
|
||||
| `ok` | S→C | Generic success response to a client request. |
|
||||
| `error` | S→C | Error response. Contains `code` and `msg`. |
|
||||
|
||||
> **Note on `client/connected` and `client/disconnected`:** These opcodes exist in the client JavaScript but were not observed during testing with player-role connections. They may only be delivered to the host connection, or may require specific server configuration.
|
||||
> **Important: `client/connected` and `client/disconnected` are NOT delivered to player connections.** Extensive testing across multiple rooms confirmed that these opcodes never fire for players joining, leaving, or reconnecting — even with graceful WebSocket close. They exist in the client JavaScript and may be delivered to the **host** connection only, but this could not be verified. Players learn about joins exclusively through `textDescriptions` entity updates ("X joined."). There is **no notification mechanism for player disconnection** — Jackbox's design holds player slots open for reconnection indefinitely, and the concept of "leaving" does not exist in the UI or protocol.
|
||||
|
||||
#### Entity Operations (Client → Server)
|
||||
|
||||
@@ -418,6 +433,7 @@ const ws = new WebSocket(url, ['ecast-v0'], {
|
||||
| 2003 | `invalid opcode` | Unrecognized opcode sent |
|
||||
| 2007 | `entity value is not of type {expected}` | Type mismatch (e.g., using `text/get` on an `object` entity) |
|
||||
| 2023 | `permission denied` | Operation not allowed for this client's role |
|
||||
| 2027 | `the room has already been closed` | Room ended but REST API may still return room info (REST/WS state divergence) |
|
||||
| — | `only the host can close the room` | Specific error for `room/exit` from non-host |
|
||||
|
||||
---
|
||||
@@ -624,16 +640,37 @@ The `room` entity transitions through these states:
|
||||
|
||||
### Reconnection
|
||||
|
||||
If a player reconnects with the same `deviceId` (browser cookie/localStorage), the server sends `client/welcome` with:
|
||||
Players can reconnect to their existing session using the `secret` and `id` from their original `client/welcome` message. There are two reconnection methods:
|
||||
|
||||
**Method 1 — Via `secret` and `id` query params (programmatic):**
|
||||
|
||||
```
|
||||
wss://{host}/api/v2/rooms/{code}/play?role=player&name={name}&format=json&secret={secret}&id={id}
|
||||
```
|
||||
|
||||
**Method 2 — Via `deviceId` (browser-based):**
|
||||
|
||||
If the browser retains the same `deviceId` (stored in cookies/localStorage), the server automatically matches the reconnecting client to their previous session. The jackbox.tv UI shows a "RECONNECT" button instead of "PLAY" when it detects an existing session.
|
||||
|
||||
In both cases, the server responds with `client/welcome` containing:
|
||||
- `reconnect: true`
|
||||
- Same `id` and `secret` as the original session
|
||||
- Full current state of all entities (not just deltas)
|
||||
- Full current state of all entities (complete snapshot, not deltas)
|
||||
|
||||
### Disconnection
|
||||
### Disconnection (or lack thereof)
|
||||
|
||||
Graceful disconnection (WebSocket close code 1000) is processed immediately. Ungraceful disconnection (process kill, network drop) takes 30-60 seconds for the server to detect.
|
||||
**Jackbox has no concept of "leaving" or "disconnecting."** When a player's WebSocket closes (gracefully or otherwise), their player slot is held open indefinitely for reconnection. From the game's perspective, the player is still "in the game" — just temporarily unreachable.
|
||||
|
||||
The `connections` REST endpoint reflects connection count changes. The `here` field in `client/welcome` shows stale connections until the server cleans them up.
|
||||
Key behaviors:
|
||||
- The `connections` REST endpoint count does **not** decrease when a player's WebSocket closes.
|
||||
- The `here` field in `client/welcome` continues to list disconnected players.
|
||||
- No `client/disconnected` event is sent to other players.
|
||||
- No `textDescriptions` update is generated for disconnections.
|
||||
- The player's `player:{id}` entity remains intact with its full state.
|
||||
|
||||
The only way a player slot is freed is if the room itself is destroyed (host closes the game) or the room times out.
|
||||
|
||||
> **REST/WebSocket state divergence:** A room can be reported as existing by the REST API (`GET /rooms/{code}` returns 200) while the WebSocket layer considers it ended (error code 2027). Always verify with a WebSocket connection attempt if the REST response seems stale.
|
||||
|
||||
---
|
||||
|
||||
@@ -645,38 +682,53 @@ Answers to common questions about managing players and rooms.
|
||||
|
||||
**Join detection:**
|
||||
- **WebSocket (best):** Watch for `textDescriptions` entity updates with `category: "TEXT_DESCRIPTION_PLAYER_JOINED"` or `"TEXT_DESCRIPTION_PLAYER_JOINED_VIP"`.
|
||||
- **WebSocket (initial):** The `here` field in `client/welcome` lists all currently connected users on first connect.
|
||||
- **REST (polling):** `GET /api/v2/rooms/{code}/connections` returns the total connection count.
|
||||
- **WebSocket (initial):** The `here` field in `client/welcome` lists all registered players (see caveats below).
|
||||
- **REST (polling):** `GET /api/v2/rooms/{code}/connections` — count increases when a new player joins.
|
||||
|
||||
**Leave detection:**
|
||||
- **REST (polling):** Poll `GET /api/v2/rooms/{code}/connections` and compare counts. This is currently the most reliable method.
|
||||
- **WebSocket:** `client/disconnected` opcode exists in the protocol but was not reliably observed during testing. May only be sent to the host.
|
||||
- **Caveat:** Ungraceful disconnects can take 30-60 seconds to be reflected.
|
||||
**Leave detection — the hard problem:**
|
||||
|
||||
Jackbox does not have a concept of "leaving." Player slots are held open indefinitely for reconnection. There is:
|
||||
- **No** `client/disconnected` event sent to players.
|
||||
- **No** change in the `connections` REST count when a player's WebSocket closes.
|
||||
- **No** `textDescriptions` update for disconnections.
|
||||
- **No** change in the `here` field — disconnected players remain listed.
|
||||
|
||||
In other words, **the ecast API provides no mechanism to detect that a player has disconnected.** The game treats all players as permanently in the room once joined. This is by design — the Jackbox UI has no "leave" button; a player who closes their browser can always reconnect.
|
||||
|
||||
**Possible workarounds:**
|
||||
- Monitor game-specific player entity state changes (e.g., a drawing game might detect timeouts for players who don't submit).
|
||||
- The game host (console) may have internal logic to handle absent players, but this is not exposed through the ecast API.
|
||||
|
||||
### How to count players
|
||||
|
||||
**Method 1 — REST `/connections` (simplest, recommended):**
|
||||
**Counting total player slots (who has ever joined):**
|
||||
|
||||
**Method 1 — REST `/connections`:**
|
||||
|
||||
```
|
||||
GET /api/v2/rooms/{code}/connections → {"connections": N}
|
||||
```
|
||||
|
||||
`N` includes host (1) + players + audience + internal connections (shards). To get player count: `N - 1 - audienceCount - shardCount`. For a rough estimate without audience: `N - 1`.
|
||||
`N` = host(1) + all player slots ever allocated + audience + shards. This count **does not decrease** when players disconnect. `N - 1` gives the approximate number of player slots allocated.
|
||||
|
||||
**Method 2 — WebSocket `here` field (most accurate at connection time):**
|
||||
**Method 2 — WebSocket `here` field:**
|
||||
|
||||
The `client/welcome` message includes a `here` object mapping session IDs to roles:
|
||||
|
||||
```json
|
||||
{
|
||||
"1": {"id": 1, "roles": {"host": {}}},
|
||||
"2": {"id": 2, "roles": {"player": {"name": "PROBE1"}}},
|
||||
"3": {"id": 3, "roles": {"player": {"name": "PROBE3"}}},
|
||||
"4": {"id": 4, "roles": {"shard": {}}}
|
||||
"2": {"id": 2, "roles": {"player": {"name": "P1"}}},
|
||||
"3": {"id": 3, "roles": {"player": {"name": "P2"}}},
|
||||
"4": {"id": 4, "roles": {"player": {"name": "P3"}}}
|
||||
}
|
||||
```
|
||||
|
||||
Count entries where `roles` contains `player`. This gives exact player count at connection time but doesn't update in real-time.
|
||||
Count entries where `roles` contains `player`. This includes **both connected and disconnected** players — `here` reflects all allocated slots, not just active WebSocket connections. The `here` field excludes the connecting client itself (you don't see yourself).
|
||||
|
||||
**Counting currently-connected players:**
|
||||
|
||||
There is no reliable API method to distinguish between connected and disconnected players. The `connections` count and `here` field both include disconnected players whose slots are held for reconnection.
|
||||
|
||||
**Method 3 — WebSocket `room/get-audience` (audience count only):**
|
||||
|
||||
@@ -859,18 +911,20 @@ The `reconnect: true` flag distinguishes reconnections from new joins. Same `id`
|
||||
}
|
||||
```
|
||||
|
||||
### Connected User Roles in `here`
|
||||
### Registered User Roles in `here`
|
||||
|
||||
```json
|
||||
{
|
||||
"1": {"id": 1, "roles": {"host": {}}},
|
||||
"2": {"id": 2, "roles": {"player": {"name": "PROBE1"}}},
|
||||
"3": {"id": 3, "roles": {"player": {"name": "PROBE3"}}},
|
||||
"4": {"id": 4, "roles": {"shard": {}}}
|
||||
"2": {"id": 2, "roles": {"player": {"name": "P1"}}},
|
||||
"3": {"id": 3, "roles": {"player": {"name": "P2"}}},
|
||||
"4": {"id": 4, "roles": {"player": {"name": "P3"}}}
|
||||
}
|
||||
```
|
||||
|
||||
Known roles:
|
||||
- `host` — The game console/PC running the game
|
||||
- `player` — A player joined via jackbox.tv (includes `name`)
|
||||
- `shard` — Internal connection (possibly audience aggregator)
|
||||
- `shard` — Internal connection (appears when audience is connected, possibly audience aggregator)
|
||||
|
||||
> **`here` includes disconnected players.** Tested by connecting 3 players, closing all their WebSockets, then having a new player connect. The new player's `here` field listed all 3 previous players despite none of them having active WebSocket connections. This is consistent with Jackbox's slot-reservation model where player slots persist for reconnection.
|
||||
|
||||
Reference in New Issue
Block a user