Multiplayer
The Olympia API is a Hono Worker hosting five Durable Objects: one per game (chess, pool, poker, snake) plus a singleton MatchmakingDO per game. Everything is anonymous-first.
Anonymous guest sessions
POST /auth/guest issues a 24-hour JWT. No email, no password. The token carries a sessionId (UUID) and a generated displayName (Guest####). Rate-limited to 5 issues per IP per hour.
A second endpoint, POST /auth/refresh, rotates the token 1 hour before expiry. If the refresh fails with 403 banned, the client clears local state and surfaces a one-time toast.
Rooms and codes
Three lobby flows, all anonymous:
| Action | Method + path | Effect |
|---|---|---|
| Create | POST /lobby/:game/create | Generates a 6-char unambiguous code (charset ABCDEFGHJKMNPQRSTUVWXYZ23456789 — no 0/O/1/I/L), writes a D1 row, returns wsUrl. |
| Join | POST /lobby/:game/join | Validates the code, increments seat, returns wsUrl. |
| Quickmatch | GET /lobby/:game/quickmatch (WS) | Enters the matchmaking queue. The DO pairs you with the next opponent. |
Codes are case-insensitive; the API uppercases on read.
Reconnect handling
If your WebSocket drops while a game is in progress, the client retries with a 1s / 2s / 4s back-off. The server holds the seat open for 30 seconds total. If the opponent reconnects in that window, play resumes; if not, the room transitions to completed and an opponent-left event is broadcast.
Building your own multiplayer layer
@orphnet/games-shared exports a RoomLifecycleHooks interface. Implement it once for your stack and the per-game DOs will call into your hooks at the right moments:
export interface RoomLifecycleHooks {
onPlayerJoined(seat: number, session: SessionRef): Promise<void>
onPlayerLeft(seat: number, session: SessionRef): Promise<void>
onRoomStarted(): Promise<void>
onRoomCompleted(replayData: Uint8Array, meta: ReplayMeta): Promise<void>
}
Olympia's wiring writes replays to R2 and updates a D1 index — you can substitute Postgres, S3, or anything else.
Rate limits & abuse
| Route | Limit | Window | Identifier |
|---|---|---|---|
POST /auth/guest | 5 | 1h | IP hash |
POST /auth/refresh | 60 | 1h | session |
POST /lobby/:game/create | 10 | 1h | IP hash |
POST /lobby/:game/join | 30 | 1h | IP hash |
A banned_ip_hashes table holds manually-applied bans. IP addresses are HMAC-SHA256 hashed before storage; cleartext never persisted.
Privacy
- No accounts, no email, no PII.
- 30-minute room idle expiry.
- 7-day session expiry.
- 30-day replay expiry via R2 lifecycle rules.
- No cookies are set. The JWT lives in
localStorage.