try to fix this spaghetti of a code
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Architecture and operations
|
||||
|
||||
`createApplication(options)` constructs an isolated application; importing `gambling.js` starts no listener or timers. Configuration, repository, clock, deck generation, bcrypt cost, and timing limits are injectable for deterministic tests.
|
||||
|
||||
| Module | Responsibility |
|
||||
| --- | --- |
|
||||
| `lib/repository.js` | SQLite schema migration, transactions, ledger, idempotency, interrupted-hand refunds |
|
||||
| `lib/database-lock.js` | Enforce one process owner per database, recover locks after a crashed PID |
|
||||
| `lib/sessions.js` | Password/session verification, hashed session storage, rotation/revocation events |
|
||||
| `lib/lobbies.js` | Membership, command queues, staged state, timer scheduling, post-commit broadcasts |
|
||||
| `lib/engine.js` | Synchronous poker transitions with injected clock and deck |
|
||||
| `lib/poker.js` | Hand evaluation, shuffling, contribution-layer payouts |
|
||||
| `lib/http.js` / `lib/transport.js` | HTTP / WebSocket validation, limits, subscription, personalized output |
|
||||
| `public/protocol.js` / `public/client.js` | Shared request contracts and resilient browser connection |
|
||||
|
||||
Commands lock their lobby and account; membership mutations also share a membership queue. Session replacement and authorized actions share a session/account queue. SQLite operations share a database queue, so reads cannot see partial multi-statement transactions. Each command clones state, validates and calculates its complete result, and checks that account deltas plus the escrow delta sum to zero. A transaction persists all balance deltas, the ledger, lobby snapshot, and idempotency record. Only then is memory replaced and state broadcast.
|
||||
|
||||
Timers have hand IDs, deadlines, and lobby revisions. Stale timers are no-ops. Failed timer transactions retry with the same identity and the original unchanged state. There are no detached settlement promises. The engine can settle and move into intermission within the same atomic action transaction.
|
||||
|
||||
SQLite uses WAL and synchronous=FULL. The startup recovery transaction refunds the contributions of all persisted active hands and clears lobbies. Deleting those snapshots in the same transaction makes recovery repeatable without duplicate refunds. The server deliberately supports **one process per database**, not clustered workers; a transactionally claimed SQLite ownership row enforces this. Ownership held by a dead PID is reclaimed atomically. If an OS PID has been reused by another live process, startup fails closed; verify no server owns the file before clearing the runtime_owner row manually. Keep the database on a local filesystem.
|
||||
|
||||
## Upgrades and migration
|
||||
|
||||
Stop the application before upgrading or importing. Back up the database and any legacy JSON outside the checkout. On schema version 0 upgrades, SQLite creates an additional `*.backup` snapshot before rebuilding the user table. The transactional migration preserves usernames/password hashes/balances, rejects invalid balances, discards raw stored sessions, and advances `PRAGMA user_version` to 1. Users sign in again. Databases from newer versions are rejected.
|
||||
|
||||
Legacy JSON is never imported automatically. Run `npm run migrate:legacy -- path/to/data.json` while the server is stopped. The importer validates all records first, preserves the source, creates a content-verified backup, then imports every row and records the source hash in one transaction. Username conflicts fail the entire import. Repeating a successful identical file is a no-op. Legacy tokens/lobby references are not imported. Protect backups because legacy JSON may contain plaintext passwords.
|
||||
|
||||
Run only the migration command against the target database; development and tests use separate databases. Test suites use in-memory or temporary files and do not touch the application database or `data.json`.
|
||||
|
||||
## Deployment and maintenance
|
||||
|
||||
Set `PUBLIC_ORIGIN` to the exact HTTPS origin without a trailing slash when behind a reverse proxy. Set `TRUST_PROXY` only to the IPs/subnets of proxies you control; it defaults to false. Do not trust arbitrary forwarded headers. Bind `HOST` as appropriate (the local default is 127.0.0.1). Set a randomly generated `TOKEN_SECRET` of at least 32 characters. Changing it invalidates sessions.
|
||||
|
||||
CI runs syntax/contract checks, unit/integration tests, Chromium end-to-end tests, and npm audit. Scheduled CI and Dependabot check dependencies weekly; review lockfile diffs and run the full suite before merging updates. High/critical audit findings block CI. Review moderate findings rather than applying blind `--force` updates. `npm ci` uses the checked-in lockfile.
|
||||
|
||||
The ledger and idempotency records intentionally have no automatic deletion. Monitor file growth; any future retention policy must preserve replay safety and accounting evidence. Keep backups, and test restoring them. This is virtual-chip software with an explicit simplified ruleset, not a real-money payment or regulatory platform.
|
||||
|
||||
Testing references: [Playwright server setup](https://playwright.dev/docs/test-webserver), [isolated browser contexts](https://playwright.dev/docs/browser-contexts), and [npm audit](https://docs.npmjs.com/cli/v11/commands/npm-audit/).
|
||||
@@ -0,0 +1,42 @@
|
||||
# HTTP and WebSocket protocol
|
||||
|
||||
`swagger.json` describes HTTP request and response bodies. Run `npm run docs` to regenerate it from the same command schemas used by both transports. `npm run check` fails when generated contracts are stale. The running documentation is available at `/swagger-ui`.
|
||||
|
||||
`docs/websocket.schema.json` is JSON Schema draft 2020-12: its root validates incoming messages; `$defs` describes every outgoing message. Runtime request validation lives in the dependency-free `public/protocol.js`, shared by server and browser.
|
||||
|
||||
## Authentication
|
||||
|
||||
Register/login with JSON `{ "username": "alice", "password": "..." }`. The response contains `{username, token}` and sets a 30-minute HttpOnly/SameSite=Strict cookie. Cookies are Secure when Express recognizes the request as HTTPS under the configured trusted proxy rules. Bearer clients use `Authorization: Bearer <token>`.
|
||||
|
||||
WebSockets connect at `/` on the same host. Browser upgrades must send the expected Origin. Non-browser upgrades may omit Origin only with a bearer header. Wait for `auth:success` before sending commands. Expired, replaced, and logged-out sessions close immediately with code 1008. Heartbeats remove dead peers. There are 200 total connections, 10 per direct peer IP, 30 messages/second/connection, eight queued commands/connection, and 16 KiB message/body limits. These limits are injectable application configuration; proxy-backed installations must account for the direct peer IP limit.
|
||||
|
||||
Authentication routes allow 20 attempts per direct/trusted-proxy client IP per 15 minutes, returning HTTP 429 plus Retry-After when exhausted. WebSocket over-limit messages close with 1008; excess upgrades return HTTP 429. Slow clients exceeding 256 KiB buffered output close with 1013.
|
||||
|
||||
## Mutating commands
|
||||
|
||||
All mutations use a unique `requestId` (8-128 letters, digits, hyphens, or underscores). Use a UUID. For HTTP, POST the fields below without `type` to the corresponding route. The old GET mutations return 405.
|
||||
|
||||
| Type | HTTP route | Other fields |
|
||||
| --- | --- | --- |
|
||||
| `lobby:create` | `/lobby/create` | optional boolean `private` |
|
||||
| `lobby:join` | `/lobby/join` | `lobbyId` |
|
||||
| `lobby:leave` | `/lobby/leave` | `lobbyId` |
|
||||
| `poker:start` | `/poker/start` | `lobbyId` |
|
||||
| `poker:action` | `/poker/action` | `lobbyId`, `action` (`hit`, `fold`, `bet`), `payload: {amount}` for bets |
|
||||
|
||||
Lobby IDs contain 16 lowercase hexadecimal characters. Unknown fields, malformed types, fractional bets, and missing fields are rejected. Failed business-rule validation does not consume a request ID. Committed commands retain their durable idempotency records. Retry an identical command using the same request ID after an uncertain response; reusing the ID with a different command returns `IDEMPOTENCY_CONFLICT`. A committed command replay returns its original acknowledgement and never repeats its effects, including after restart. Do not reuse an ID for a new intention.
|
||||
|
||||
The browser retries pending commands on reconnect with the same ID, using 0.5/1/2/4/5-second connection backoff. After five failed reconnects it asks for reload. It waits up to 30 seconds for an acknowledgement. On timeout, check restored state before creating a new command.
|
||||
|
||||
`poker:join` is a read/subscription message `{type, lobbyId}` without a request ID. It requires existing membership and never starts a game. Reconnecting automatically subscribes to the account's current lobby and sends a fresh snapshot.
|
||||
|
||||
## Server events
|
||||
|
||||
- `auth:success`: authenticated `username`.
|
||||
- `command:ack`: `requestId`, `commandType`, `lobbyId`, `username`. HTTP returns the same fields with the command name in `type` instead.
|
||||
- `lobby:state`: players, host, visibility, start eligibility, gameStarted, and gameUrl.
|
||||
- `lobby:left`: lobbyId. All tabs belonging to the departing account are detached.
|
||||
- `poker:state`: personalized hand, community cards, queue/turn, phase/street/round, stack/contribution, pot/table bet/call amount, minimumRaise/canRaise, deadlines, and completed-hand payouts/showdown. Opponents' private cards appear only at showdown. Full field definitions are in the schema.
|
||||
- `error`: stable `code`, human-readable `message`, and `requestId` when available. HTTP uses `{code, error}`.
|
||||
|
||||
Error codes include `INVALID_MESSAGE`, `INVALID_JSON`, `INVALID_CREDENTIALS`, `UNAUTHORIZED`, `RATE_LIMITED`, `ORIGIN_DENIED`, `USERNAME_EXISTS`, `ALREADY_SEATED`, `HAND_PENDING`, `LOBBY_NOT_FOUND`, `LOBBY_FULL`, `NOT_SEATED`, `HOST_REQUIRED`, `GAME_STARTED`, `NOT_ENOUGH_PLAYERS`, `WRONG_PHASE`, `NOT_YOUR_TURN`, `INVALID_BET`, `MINIMUM_RAISE`, `RAISE_NOT_REOPENED`, `IDEMPOTENCY_CONFLICT`, and `INTERNAL_ERROR`. HTTP status codes are declared in OpenAPI. Storage failures never publish uncommitted state.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Poker rules
|
||||
|
||||
This application plays an **ante-based no-limit community-card variant**, not casino Texas Hold'em with blinds. All amounts are integer virtual chips. The rules are implemented in `lib/engine.js`.
|
||||
|
||||
- Accounts start with 200 chips. Each hand requires a 10-chip ante; players below 10 watch. Tables have at most 10 seats.
|
||||
- After a 30-second intermission, eligible players receive two private cards. The dealer marker rotates one seat; the first solvent player to its left acts first on every street. There are no small/big blinds or burn cards.
|
||||
- Preflop, flop (three cards), turn, and river each have a betting round. At showdown, the best five of seven cards wins. Aces may be low in A-2-3-4-5 straights. Suits do not break ties.
|
||||
- **Check/call** (`hit` in the protocol) checks for free or pays the smaller of the outstanding call and the remaining stack. **Fold** gives up eligibility. **Bet** specifies chips to add now, not a target total.
|
||||
- An opening bet is at least 10. A full raise adds at least the previous full raise above the current table bet. A smaller increase is allowed only if it consumes the player's entire stack.
|
||||
- A short all-in requires others to match the increased bet but does not reopen raises for someone who has already acted. Cumulative short raises reopen action once the increase faced since that player's last action reaches the previous full-raise size.
|
||||
- A lone solvent player may call outstanding chips but may not raise into opponents who are all-in. If nobody can act, remaining board cards run out immediately.
|
||||
- After 30 seconds, a turn checks when free and folds when facing a bet. Timeout never commits additional chips.
|
||||
|
||||
## Pots and leaving
|
||||
|
||||
Each distinct contribution level creates a pot. Folded players' chips contribute but their hands cannot win. Eligible tied winners split each layer. Odd chips go to eligible winners in seat order starting left of the dealer. Unmatched chips return to their contributor.
|
||||
|
||||
If no live hand is eligible for a contribution layer, that layer is refunded to its contributors. This explicit house rule also handles abandoned/dead hands and forfeited high contribution layers; it avoids destroying chips.
|
||||
|
||||
Leaving immediately folds, removes lobby membership, and keeps committed contributions in the hand. The account cannot join another table or rejoin the same hand until that hand settles. This prevents spending the same balance while an old table still holds a claim. Uncommitted chips remain in the account. A player joining an already-running hand spectates until the next deal.
|
||||
|
||||
A disconnect has a 10-second grace period. Reconnecting restores the same hand; if the last connection stays absent, the server leaves/folds the account through the same command queue.
|
||||
|
||||
## Restart policy
|
||||
|
||||
Committed bets are held in durable escrow; `/balance` and leaderboard values exclude that escrow. On restart, interrupted hands are cancelled, contributions are refunded exactly once, and lobbies are cleared. Completed payouts remain paid. The server does not attempt to resume partially played hands after a restart.
|
||||
@@ -0,0 +1,654 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "WebSocket protocol",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"requestId"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "lobby:create"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_-]{8,128}$"
|
||||
},
|
||||
"private": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId",
|
||||
"requestId"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "lobby:join"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{16}$"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_-]{8,128}$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId",
|
||||
"requestId"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "lobby:leave"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{16}$"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_-]{8,128}$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId",
|
||||
"requestId"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "poker:start"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{16}$"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_-]{8,128}$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "poker:join"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{16}$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId",
|
||||
"requestId",
|
||||
"action"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "poker:action"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-f0-9]{16}$"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_-]{8,128}$"
|
||||
},
|
||||
"action": {
|
||||
"enum": [
|
||||
"hit",
|
||||
"fold",
|
||||
"bet"
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"$defs": {
|
||||
"auth:success": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "auth:success"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"username"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"command:ack": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "command:ack"
|
||||
},
|
||||
"commandType": {
|
||||
"type": "string"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"commandType",
|
||||
"lobbyId",
|
||||
"username",
|
||||
"requestId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "error"
|
||||
},
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"code",
|
||||
"message"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"lobby:left": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "lobby:left"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"lobby:state": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "lobby:state"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"players": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"isPublic": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canStart": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"gameStarted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"gameUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId",
|
||||
"host",
|
||||
"players",
|
||||
"isPublic",
|
||||
"canStart",
|
||||
"gameStarted",
|
||||
"gameUrl"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"poker:state": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "poker:state"
|
||||
},
|
||||
"lobbyId": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"players": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"round": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"pot": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"currentBet": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"bet": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"communityCards": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rank": {
|
||||
"enum": [
|
||||
"A",
|
||||
"K",
|
||||
"Q",
|
||||
"J",
|
||||
"10",
|
||||
"9",
|
||||
"8",
|
||||
"7",
|
||||
"6",
|
||||
"5",
|
||||
"4",
|
||||
"3",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"suit": {
|
||||
"enum": [
|
||||
"S",
|
||||
"H",
|
||||
"D",
|
||||
"C"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"rank",
|
||||
"suit"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"hand": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rank": {
|
||||
"enum": [
|
||||
"A",
|
||||
"K",
|
||||
"Q",
|
||||
"J",
|
||||
"10",
|
||||
"9",
|
||||
"8",
|
||||
"7",
|
||||
"6",
|
||||
"5",
|
||||
"4",
|
||||
"3",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"suit": {
|
||||
"enum": [
|
||||
"S",
|
||||
"H",
|
||||
"D",
|
||||
"C"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"rank",
|
||||
"suit"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"currentPlayer": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"isYourTurn": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"queue": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"folded": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"street": {
|
||||
"enum": [
|
||||
"intermission",
|
||||
"preflop",
|
||||
"flop",
|
||||
"turn",
|
||||
"river"
|
||||
]
|
||||
},
|
||||
"phase": {
|
||||
"enum": [
|
||||
"intermission",
|
||||
"waiting",
|
||||
"betting"
|
||||
]
|
||||
},
|
||||
"lastAction": {
|
||||
"type": "string"
|
||||
},
|
||||
"yourContribution": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"yourStack": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"isSpectator": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"callAmount": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"minimumStake": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"minimumRaise": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"canRaise": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"nextRoundStartsAt": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"turnEndsAt": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"winner": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"showdown": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"player": {
|
||||
"type": "string"
|
||||
},
|
||||
"hand": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rank": {
|
||||
"enum": [
|
||||
"A",
|
||||
"K",
|
||||
"Q",
|
||||
"J",
|
||||
"10",
|
||||
"9",
|
||||
"8",
|
||||
"7",
|
||||
"6",
|
||||
"5",
|
||||
"4",
|
||||
"3",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"suit": {
|
||||
"enum": [
|
||||
"S",
|
||||
"H",
|
||||
"D",
|
||||
"C"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"rank",
|
||||
"suit"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rank": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"score": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
}
|
||||
},
|
||||
"comboCards": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rank": {
|
||||
"enum": [
|
||||
"A",
|
||||
"K",
|
||||
"Q",
|
||||
"J",
|
||||
"10",
|
||||
"9",
|
||||
"8",
|
||||
"7",
|
||||
"6",
|
||||
"5",
|
||||
"4",
|
||||
"3",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"suit": {
|
||||
"enum": [
|
||||
"S",
|
||||
"H",
|
||||
"D",
|
||||
"C"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"rank",
|
||||
"suit"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"rank",
|
||||
"name",
|
||||
"score",
|
||||
"comboCards"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"player",
|
||||
"hand",
|
||||
"result"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"payouts": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9007199254740991
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"lobbyId",
|
||||
"username",
|
||||
"players",
|
||||
"round",
|
||||
"pot",
|
||||
"currentBet",
|
||||
"bet",
|
||||
"communityCards",
|
||||
"hand",
|
||||
"currentPlayer",
|
||||
"isYourTurn",
|
||||
"queue",
|
||||
"folded",
|
||||
"street",
|
||||
"phase",
|
||||
"lastAction",
|
||||
"yourContribution",
|
||||
"yourStack",
|
||||
"isSpectator",
|
||||
"callAmount",
|
||||
"minimumStake",
|
||||
"minimumRaise",
|
||||
"canRaise",
|
||||
"nextRoundStartsAt",
|
||||
"turnEndsAt",
|
||||
"winner",
|
||||
"showdown",
|
||||
"payouts"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user