summaryrefslogtreecommitdiffstats
path: root/docs/WEB-PLAN.md
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
committersalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
commit058432130628d00764663ecf819a9adc6793768d (patch)
treed3f49f111fbfaa48647cca2600adcf9017af2d30 /docs/WEB-PLAN.md
parentweb: serve latest.php through PHP and refuse stray PHP files (diff)
downloadzagan-bet-058432130628d00764663ecf819a9adc6793768d.tar.gz
zagan-bet-058432130628d00764663ecf819a9adc6793768d.zip
v5.0.0: protocol v5 and the browser frontend
Protocol v5 replaces newline lines with bounded [u32 len][text] frames and REQ/RES/EVT envelopes. Requests carry correlation ids and the server replays the last 256 replies per connection, so a gateway retry cannot double-debit and a late reply cannot clobber a newer request. HELLO/PONG carry server time; crash rounds announce started_at and push coalesced 10 Hz ticks. Crash now commits sha256(seed) before a round and reveals the seed when it ends, so clients recompute the crash point. Adds the webd/ gateway: one blocking thread, one ClientCore and one pinned upstream TLS connection per session, an HttpOnly session cookie, takeover, and a 60-second grace window. The browser sends Command JSON and renders View JSON; it never speaks the game protocol or stores credentials. Adds the vanilla TypeScript browser app at /play/ (login, lobby, all four games, history, notices, crash verification), the nginx /play and /ws routes, and the headless DOM smoke test. webd and the app stay out of the TUI/GUI/server build graph. The client/server version moves to 5.0.0 in lockstep; v4 peers are refused at HELLO.
Diffstat (limited to 'docs/WEB-PLAN.md')
-rw-r--r--docs/WEB-PLAN.md129
1 files changed, 129 insertions, 0 deletions
diff --git a/docs/WEB-PLAN.md b/docs/WEB-PLAN.md
new file mode 100644
index 0000000..895687b
--- /dev/null
+++ b/docs/WEB-PLAN.md
@@ -0,0 +1,129 @@
1# Browser frontend plan
2
3Status: v1 implemented in 5.0.0. This is the working plan and the deployment
4contract; see `CONTEXT.md` and ADRs 0003/0004 for the decisions.
5
6## Shape
7
8```
9browser ──wss──> nginx ──ws──> webd ──TLS──> game server
10 /play/ /ws 127.0.0.1:8890 127.0.0.1:11525
11 static files gateway one thread per authoritative rules,
12 browser session balances, crash clock
13```
14
15- `web/play/` is a static TypeScript app. It sends `Command` JSON and renders
16 `View` JSON. It has no rules and no wire parser.
17- `webd/` is a workspace member. It owns one `ClientCore`, one upstream TLS
18 connection and a 60-second grace window per browser session.
19- `src/protocol.rs` is v5: bounded frames, `REQ`/`RES`/`EVT`, request ids,
20 server time and crash commit–reveal.
21
22## Phases
23
241. **Protocol v5** — frames, envelope, correlation, replay cache, clock sync,
25 ticks, commit–reveal, TUI/GUI updates. Done in the 5.0.0 change.
262. **Gateway** — WS accept, cookie session registry, takeover, grace, pinned
27 upstream, `Command`/`View` JSON. Done in the 5.0.0 change.
283. **Web shell** — login/register, lobby, history, notices, route at `/play/`.
29 Done in the 5.0.0 change.
304. **Games** — slots, roulette (felt + chips), blackjack, crash (curve +
31 verification). Done in the 5.0.0 change.
325. **Deployment** — nginx routes, static build, systemd unit, verification in
33 `scripts/deploy.sh`. Done in the 5.0.0 change.
34
35## Browser contract
36
37Browser → gateway:
38
39```json
40{"type":"command","command":{"Login":{"user":"alice","credential":"<sha256 hex>"}}}
41{"type":"command","command":{"Play":{"game":"crash","bet":10}}}
42{"type":"command","command":"Start"}
43{"type":"ping"}
44```
45
46`Command` uses serde's external tagging because several variants carry a
47primitive or a string. Unit variants are bare strings (`"Spin"`, `"Start"`).
48
49Gateway → browser:
50
51```json
52{"type":"hello","version":"5.0.0","grace_seconds":60,"user":"alice","time":1700000000000}
53{"type":"view","view":{"screen":"lobby", "...": "..."}}
54{"type":"notice","text":"Logged in as alice"}
55{"type":"error","text":"Upstream connection failed: ..."}
56{"type":"replaced"}
57{"type":"pong"}
58```
59
60`View` is internally tagged by `screen` (`lobby`, `history`, `slots`,
61`roulette`, `blackjack`, `crash`). The browser renders each screen; it never
62computes a result.
63
64The cookie is `zagan_session`, HttpOnly, SameSite=Strict, Secure on HTTPS. It
65is set on the WebSocket handshake. It covers refresh and the grace window only;
66there is no remember-me.
67
68## Build and run
69
70```sh
71# static app
72cd web/play
73npm ci
74npm run build # writes app.js; commit it
75
76# gateway
77cargo build -p zagan-bet-webd --release
78ZAGAN_UPSTREAM=127.0.0.1:11525 \
79ZAGAN_UPSTREAM_FINGERPRINT=<64 hex> \
80ZAGAN_WEB_ORIGIN=https://zagan.2137697.xyz \
81ZAGAN_WEB_BIND=127.0.0.1:8890 \
82target/release/zagan-bet-webd
83```
84
85The fingerprint is the same SPKI SHA-256 the desktop client shows:
86
87```sh
88openssl x509 -in cert.pem -pubkey -noout \
89 | openssl pkey -pubin -outform DER \
90 | sha256sum
91```
92
93Test against a staging site with
94`ZAGAN_WEB_ORIGIN=http://127.0.0.1:8899 ZAGAN_WEB_COOKIE_SECURE=0`.
95
96## Security boundaries
97
98- The gateway binds loopback. nginx terminates TLS and is the only public
99 entry. Do not expose `ZAGAN_WEB_BIND` to the internet.
100- The upstream key is pinned; there is no TOFU and no unsafe mode. A mismatch
101 logs both fingerprints and refuses credentials.
102- The browser has no certificate UI and no credential storage. XSS cannot read
103 the HttpOnly cookie; the backend still must escape every notice.
104- The browser speaks JSON, not the game wire. A malicious page cannot use the
105 gateway as a protocol proxy: `Origin` is checked and the session cookie is
106 SameSite=Strict.
107- The web app is play-money only. No deposits, withdrawals, KYC or real-money
108 claims belong in v1 copy.
109
110## Failure behaviour
111
112| Event | Result |
113| --- | --- |
114| Refresh / new tab, within grace | Same `ClientCore`, current `View` restored |
115| New tab, same cookie | Old tab gets `replaced` and closes; new tab continues |
116| Socket closed, grace expires | Upstream closes; server forfeits live hand |
117| Gateway restart | Sessions lost; players log in again |
118| Upstream disconnect | Browser gets an error, core resets, reconnect attempts |
119| Browser retry of a request | Gateway does not retry; request ids are per connection |
120
121## Verification
122
123- `cargo test --workspace` covers frames, envelope parsing, idempotent replay,
124 commit verification, ticks, core routing and session JSON.
125- `web/play`: `npm run typecheck` and `npm run build`.
126- Manual WS smoke test: connect, login, play slots/crash, refresh in a live
127 crash round, verify the revealed seed.
128- `scripts/deploy.sh` checks `/play/`, `app.js`, `style.css` and the existing
129 download feed.