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/sdk
import { 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):

OptionRequiredDefaultPurpose
urlyes—engine WS endpoint, e.g. "wss://engine.example.com/ws"
tokenyes—the session token from POST /v1/sessions; sent in the subprotocol list, never in the URL
WebSocket—globalWS implementation to inject on Node (browser uses the global)
autoReconnect—truere-dial with the same session token after a drop (suppressed on close 4001 / 4002)
reconnectDelayMs—500backoff base — delay = base·2ⁿ
maxReconnectDelayMs—8000backoff cap
maxReconnectAttempts—10give up after this many failed attempts

Methods

MethodPurpose
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

EventPayloadWhen
matchFoundMatchFoundMessageseated in a new game (pair, bot fallback, tournament round) — route to the board; arrives before the room's first gameState
gameStateGameStateMessagefull 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
moveMoveMessagevalidated move (own or opponent), scoped by room
gameOverGameOverMessagethat room finished — the session socket stays open
errorErrorMessagerejected command — { code, message }; room omitted when session-scoped
drawOfferDrawOfferMessageopponent offered a draw ({ by })
drawDeclinedDrawDeclinedMessageyour 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
messageServerMessageevery 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

HelperPurpose
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
ConstantMeaning
SUBPROTOCOL"inachess.v2" — sent as [SUBPROTOCOL, token]; the server echoes only this
CLOSE_AUTH_EXPIRED4001 — token expired; do not retry, re-mint
CLOSE_REPLACED4002 — 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

Added
  • 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

Added
  • 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

Added
  • 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.
Changed
  • 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.

Breaking
  • 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.
Removed
  • 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).
Added
  • 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.
Changed
  • 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

Added
  • 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.
Changed
  • 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.
Fixed
  • 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).

Added
  • 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.