实现账号绑定、持久登录与跨设备对局恢复
CI / test (ubuntu-latest, node 22) (push) Failing after 33m10s
CI / test (ubuntu-latest, node 24) (push) Failing after 34m41s
CI / tooling (lint, imports, typecheck) (push) Failing after 36m11s
CI / test (windows-latest, node 22) (push) Canceled after 0s
CI / test (windows-latest, node 24) (push) Canceled after 0s

This commit is contained in:
herryyun committed 2026-10-14 23:22:50 +08:00
1 parent 488db49f11
commit 7c25c89992
62 files changed
+6855 -1005

No files matched your search

+22
View File
@@ -4,6 +4,26 @@ Part of [DESIGN.md](../DESIGN.md) (the index; section numbers are global).
## 8. Network protocol (shared/protocol.js is normative)
**Fork account policy (opt-in `SP_SKLAND=1`, owner request 2026-10-14).** The existing WebSocket messages are
unchanged. Hello may establish anonymous transport for account login, but room/game requests require the role
owned by a persistent, server-verified JWT cookie. The server ignores a client's claimed nickname/UUID for account
identity; it derives stable `playerId` from UID plus provider-reported server region (`channelName`). Nicknames
may repeat, and optional provider `gameId` metadata does not affect identity. The public id uses 128 SHA-256 digest
bits encoded as base64url after `a_` (24 characters), so normal field ids and per-player battle reports fit the
existing protocol limits. A reconnect token alone cannot resume
an account identity. A valid login plus the role's current reconnect token can resume its previous socket; each
game frame checks the current login record and selected role, so logout or a role switch invalidates its authority.
Cookie restoration and read-only recovery lookup never take over another device. An explicit authenticated
enter/resume POST returns the selected role's token; the existing hello then attaches it and resends room/match state.
Only after successful attachment is the account token rotated, so a stale device cannot silently retake the seat.
The client stages the entry token in memory until welcome; local multi-tab claims still protect normal reconnects.
Login lasts 30 days, survives process restart and is revoked on unbind. Room/match data remain in memory.
Recovery selectors are scoped to the selected role and the particular room/match; stale selectors return 409 without
creating a new game. The existing 10-minute / solo 24-hour disconnect windows are unchanged, and an expired seat is
removed through the lobby before its stable playerId can be reused. Account API ownership and private DB handling
are specified in [skland.md](../skland.md) §5/§9. The optional policy hooks leave module-off transport unchanged;
no simulation rule, WebSocket message or protocol-version change is involved.
Transport: one WebSocket per tab at `/ws`, JSON text frames `{ t: '<type>', ...payload }`. Client requests may carry `rid` (request id); the server's direct reply echoes `rid` (`ok` or `error`). Server pushes have no `rid`. Max frame 64 KB inbound (a compressed frame counts its inflated size); rate limit 40 msgs/s per connection (excess dropped with `error RATE`). Frames from 1 KB up are compressed with permessage-deflate (RFC 7692; `server/http/websocket.js WS_DEFLATE`: no server context takeover, a 2 KB server window, zlib memLevel 7 — about 72 KB of zlib state per connection); smaller frames go out plain, and a client that offers no compression, or caps the server window below 2 KB, gets plain frames. A measured solo session (rounds 3–5, `/healthz?traffic=1`): 173 KB of server frames → 60 KB on the wire.
### 8.1 Session & lobby
@@ -137,6 +157,8 @@ voting, refuses stale ids and cancels on disconnect/leave; DESIGN §28.21 / META
Explicit local recovery (§28.20) reuses saved hello tokens after a fresh live-tab claim check. Display names and room
codes are UI metadata only, never authentication. No new network message or nickname/IP matching is introduced.
The account-required fork replaces the visible local recovery entry with authenticated account recovery (§31.13);
the generic local helper and its module-off handshake behavior are retained.
Optional `coins: [[id, balance, cap]]` is an integer skill-currency readout (§29.6), separate from ammo. It includes zero, is stepped between snapshots and clears when omitted. Result-validation names for board operators are resolved from the server's DIY/stand-in selections, never a supplied display name.