SDK reference
@inachess/sdk — framework-agnostic (browser + Node), zero runtime dependencies. It owns the wire protocol: one session socket, auto-pong, subscription replay, and auto-reconnect with sane close-code handling.
Install & quick use
pnpm add @inachess/sdkimport { EngineClient } from "@inachess/sdk";
// Session token from POST /v1/sessions, handed to this client at login.
const client = new EngineClient({ url: "wss://engine.example.com/ws", token });
client.on("matchFound", (m) => console.log("seated:", m.matchId));
client.on("gameState", (s) => render(s));
client.on("move", (m) => render(m.fen, m.turn));
client.on("gameOver", (g) => console.log(g.result, g.reason));
client.on("drawOffer", (d) => console.log(d.by, "offered a draw"));
client.on("error", (e) => console.warn(e.code, e.message));
client.connect(); // one socket for the whole login session
client.queue({ variant: "standard", timeControl: "blitz_3_2", rated: true });
client.accept(); // ready_check gate
client.move("e2e4"); // UCI
client.resign();Constructor options
new EngineClient(options):
| Option | Required | Default | Purpose |
|---|---|---|---|
url | yes | — | engine WS endpoint, e.g. "wss://engine.example.com/ws" |
token | yes | — | the session token from POST /v1/sessions; sent in the subprotocol list, never in the URL |
WebSocket | — | global | WS implementation to inject on Node (browser uses the global) |
autoReconnect | — | true | re-dial with the same session token after a drop (suppressed on close 4001 / 4002) |
reconnectDelayMs | — | 500 | backoff base — delay = base·2ⁿ |
maxReconnectDelayMs | — | 8000 | backoff cap |
maxReconnectAttempts | — | 10 | give up after this many failed attempts |
Methods
| Method | Purpose |
|---|---|
connect() | dial with the constructor token — same call for the first dial and every manual reconnect |
close() | close the socket + stop reconnecting |
queue(opts) | enter matchmaking: { variant?, timeControl, rated?, autoBot? } — variant defaults "standard", rated true, autoBot true. Never carries a rating |
cancelQueue() | leave matchmaking |
playBot(opts) | start a bot game: { level?, timeControl, color? } — level 1..8 default 4, color default "white", always unrated |
subscribe(room?) | watch a live match · omit room to re-attach to your own game. Replayed automatically on every new socket |
unsubscribe(room) | stop watching — on your own room this tells the engine you left the board |
move(uci) | send a UCI move; promotion = 5-char UCI (e.g. e7e8q). No-op if the socket is not open |
resign() | resign the current game |
offerDraw() / acceptDraw() / declineDraw() | draw negotiation (offer is rate-limited by the engine) |
claimDraw() | claim a threefold / 50-move draw when eligible |
abort() | abort before both sides' first move |
accept() | confirm presence under a ready_check gate |
on(event, fn) / off(event, fn) | add / remove a listener |
Events
| Event | Payload | When |
|---|---|---|
matchFound | MatchFoundMessage | seated in a new game (pair, bot fallback, tournament round) — route to the board; arrives before the room's first gameState |
gameState | GameStateMessage | full snapshot of a room (own game pushed · subscribe · re-attach · gate re-broadcast) — includes the match identity (variant, timeControl, players), so a reconnecting client needs nothing remembered from matchFound |
move | MoveMessage | validated move (own or opponent), scoped by room |
gameOver | GameOverMessage | that room finished — the session socket stays open |
error | ErrorMessage | rejected command — { code, message }; room omitted when session-scoped |
drawOffer | DrawOfferMessage | opponent offered a draw ({ by }) |
drawDeclined | DrawDeclinedMessage | your draw offer was declined |
open | — | socket opened (first dial or reconnect) — re-subscribe spectates + re-queue here |
close | { code, reason, willReconnect } | socket closed |
reconnecting | { attempt } | a reconnection attempt started |
authExpired | { code, reason } | closed with 4001 — token expired; re-mint via your login flow, auto-reconnect is suppressed |
replaced | { code, reason } | closed with 4002 — the user's own newer socket took over; do not redial |
message | ServerMessage | every raw server message (debug hook) |
ℹ The SDK answers engine pings automatically (auto-pong) — pings never surface as events. Payload shapes are the wire messages in the WebSocket reference; the SDK adds nothing and hides nothing except pings.
Board helpers & constants
| Helper | Purpose |
|---|---|
parseBoard(fen) | FEN → { [square]: pieceChar } map, e.g. board["e4"] === "P" (uppercase = white) |
isPromotion(board, from, to) | true when moving from→to is a pawn reaching the last rank — gate your promotion chooser on it |
fenTurn(fen) | "white" | "black" — side to move |
FILES | ["a"…"h"] file letters |
| Constant | Meaning |
|---|---|
SUBPROTOCOL | "inachess.v2" — sent as [SUBPROTOCOL, token]; the server echoes only this |
CLOSE_AUTH_EXPIRED | 4001 — token expired; do not retry, re-mint |
CLOSE_REPLACED | 4002 — replaced by the user's own newer socket; do not retry |
Changelog
Semver. Minor = additive wire/API surface, major = breaking. Grouped Keep-a-Changelog style: Added / Changed / Removed / Fixed, plus Breaking on majors.
v1.3.0 2026-07-24
- MatchSeat now carries username — the platform display name for the seat, on every match_found and game_state. Render the opponent by it directly instead of resolving the userId yourself. Optional: omitted for a bot seat, or when the engine's platform lookup was unavailable (best-effort, never blocks the game).
v1.2.0 2026-07-23
- GameStateMessage now carries the match identity: variant, timeControl and players (MatchSeat[], white first) — the same seat shape as match_found. A reconnecting client (page reload, device switch) that lost its matchFound, or a spectator that never got one, can render the players from the snapshot alone.
v1.1.0 2026-07-23
- GameStateMessage presence-gate fields: started (clocks running?), gate, youAccepted (seat-relative), gateEndsAt (no-show deadline, unix ms) and serverNow (age the clocks against it) — render the pre-game gate instead of collecting not_started errors.
- New exported type PresenceGate — "ready_check" | "grace_period" | "none".
- New exported type MatchSeat — userId, color, plus optional rating (match-time snapshot), isBot and botLevel (Stockfish skill 1..8).
- Room pings now carry room + whiteMs / blackMs — a ~5s per-room clock resync, still answered automatically; the session-level ping stays bare.
- queue() accepts autoBot (default true) — pass false to opt out of the unrated bot fallback when unpaired too long.
- MatchFoundMessage.players is now MatchSeat[] (was an inline { userId, color }[]) — additive for consumers reading userId/color.
v1.0.0 2026-07-21breaking
Session-socket rewrite for the engine's session model (ADR 0002): one connection per login session, not per game. Requires an engine with ADR 0002 applied.
- connect() no longer takes a credential — token is a required constructor option and travels in the WebSocket subprotocol list ([SUBPROTOCOL, token]), never in the URL.
- ErrorMessage gains a required stable code (ErrorCode) — branch on code; message stays human text and may change.
- Every game message (gameState, move, gameOver, drawOffer, drawDeclined) now carries room; error carries room only when room-scoped.
- ConnectMode ({ ticket } / { reconnect } / { spectate }) — the session token replaces all three dial modes.
- reconnectToken getter and the rotating-token reconnect flow — reconnects reuse the same session token.
- seat getter — per-room seat now lives in roomState(room).
- Methods: queue(opts), cancelQueue(), subscribe(room?), unsubscribe(room), playBot(opts), roomState(room).
- Events: matchFound (MatchFoundMessage), authExpired (close 4001 — re-mint via your login flow), replaced (close 4002 — your own newer socket took over).
- Subscription replay: watched rooms (and your own board) are re-asserted on every new socket — a fresh socket starts subscribed to nothing.
- Exported constants SUBPROTOCOL ("inachess.v2"), CLOSE_AUTH_EXPIRED (4001), CLOSE_REPLACED (4002); exported types QueueOptions, PlayBotOptions, RoomState, ErrorCode, MatchFoundMessage.
- Close 4001 / 4002 suppress auto-reconnect and emit their own events — retrying either is wrong (one keeps failing, the other kicks the tab that displaced this one).
- game_over ends one room only; the session socket stays up and the user plays their next game on it — the client is never disabled by a finished game.
- Reconnect backoff resets on any inbound message (was: on game_state) — a session socket may legitimately see no game_state for a long time.
- queue never sends a rating — the engine pulls it from the platform.
v0.2.0 2026-07-14
- Draw negotiation: offerDraw(), acceptDraw(), declineDraw(), claimDraw() + events drawOffer (DrawOfferMessage) and drawDeclined (DrawDeclinedMessage).
- abort() — abort before both sides' first move; accept() — confirm presence under a ready_check gate.
- Option maxReconnectAttempts (default 10) — give up after this many consecutive failed attempts.
- GameResult gains "aborted".
- Backoff attempts reset only on a real join (game_state), not on socket open — a rejected reconnect no longer looked like success.
- connect() cancels a pending reconnect timer so it can't clobber the new socket.
- Redial detaches and closes the previous socket — its late events can't drive the client or spawn a second reconnect chain.
- Malformed / typeless inbound frames are ignored instead of reaching the handlers.
- game_over marks the game terminal — a later socket drop no longer triggers a pointless reconnect.
v0.1.0 2026-07-10
Initial release. ESM + CJS + .d.ts (tsup), zero runtime dependencies, browser + Node (injectable WebSocket).
- EngineClient with three dial modes (ConnectMode): { ticket } single-use join, { reconnect } rotating token, { spectate } spectate token.
- Methods: connect(mode), move(uci), resign(), close(), on() / off().
- Events: gameState, move, gameOver, error, message, open, close ({ code, reason, willReconnect }), reconnecting ({ attempt }).
- Auto-pong — engine latency pings answered automatically, never surfaced.
- Auto-reconnect — exponential backoff (reconnectDelayMs · 2ⁿ, capped at maxReconnectDelayMs) using the rotating reconnectToken.
- Getters reconnectToken and seat.
- Board helpers parseBoard, fenTurn, isPromotion, FILES; type PieceChar.