summaryrefslogtreecommitdiffstats
path: root/src/client/view.rs
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 /src/client/view.rs
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 '')
-rw-r--r--src/client/view.rs60
1 files changed, 45 insertions, 15 deletions
diff --git a/src/client/view.rs b/src/client/view.rs
index 4615720..0886d64 100644
--- a/src/client/view.rs
+++ b/src/client/view.rs
@@ -7,15 +7,17 @@ use crate::blackjack::BlackjackState;
7use crate::client::games::roulette::{Cursor, Direction}; 7use crate::client::games::roulette::{Cursor, Direction};
8use crate::client::games::traits::{GamePhase, GameState}; 8use crate::client::games::traits::{GamePhase, GameState};
9use crate::roulette::{BetTarget, Bets}; 9use crate::roulette::{BetTarget, Bets};
10use serde::{Deserialize, Serialize};
10 11
11#[derive(Debug, Clone, Copy, PartialEq, Eq)] 12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
12pub enum RoulettePhase { 14pub enum RoulettePhase {
13 Placing, 15 Placing,
14 Spinning, 16 Spinning,
15 Result, 17 Result,
16} 18}
17 19
18#[derive(Debug, Clone, PartialEq, Eq)] 20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct RouletteView { 21pub struct RouletteView {
20 pub bet_amount: u64, 22 pub bet_amount: u64,
21 pub balance: Option<i64>, 23 pub balance: Option<i64>,
@@ -51,7 +53,10 @@ impl RouletteView {
51 Some("result") => RoulettePhase::Result, 53 Some("result") => RoulettePhase::Result,
52 _ => RoulettePhase::Placing, 54 _ => RoulettePhase::Placing,
53 }; 55 };
54 let number = state.data.get("number").and_then(|value| value.parse().ok()); 56 let number = state
57 .data
58 .get("number")
59 .and_then(|value| value.parse().ok());
55 let color = state.data.get("color").cloned(); 60 let color = state.data.get("color").cloned();
56 let winnings = state 61 let winnings = state
57 .data 62 .data
@@ -82,14 +87,15 @@ impl RouletteView {
82 } 87 }
83} 88}
84 89
85#[derive(Debug, Clone, Copy, PartialEq, Eq)] 90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
86pub enum SlotsPhase { 92pub enum SlotsPhase {
87 Ready, 93 Ready,
88 Spinning, 94 Spinning,
89 Result, 95 Result,
90} 96}
91 97
92#[derive(Debug, Clone, PartialEq, Eq)] 98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct SlotsView { 99pub struct SlotsView {
94 pub bet: u64, 100 pub bet: u64,
95 pub balance: Option<i64>, 101 pub balance: Option<i64>,
@@ -149,7 +155,7 @@ impl SlotsView {
149 } 155 }
150} 156}
151 157
152#[derive(Debug, Clone, PartialEq, Eq)] 158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub struct BlackjackView { 159pub struct BlackjackView {
154 pub status: String, 160 pub status: String,
155 pub balance: Option<i64>, 161 pub balance: Option<i64>,
@@ -186,7 +192,7 @@ impl BlackjackView {
186 } 192 }
187} 193}
188 194
189#[derive(Debug, Clone, PartialEq)] 195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190pub struct CrashView { 196pub struct CrashView {
191 pub status: String, 197 pub status: String,
192 pub balance: Option<i64>, 198 pub balance: Option<i64>,
@@ -195,6 +201,11 @@ pub struct CrashView {
195 pub potential: u64, 201 pub potential: u64,
196 pub crash_point: Option<f64>, 202 pub crash_point: Option<f64>,
197 pub payout: u64, 203 pub payout: u64,
204 pub started_at: u64,
205 /// `sha256(seed)` for this round, before it ends.
206 pub commit: Option<String>,
207 /// The revealed seed, after the round ends.
208 pub seed: Option<String>,
198 pub error: Option<String>, 209 pub error: Option<String>,
199} 210}
200 211
@@ -232,6 +243,13 @@ impl CrashView {
232 .get("payout") 243 .get("payout")
233 .and_then(|value| value.parse::<u64>().ok()) 244 .and_then(|value| value.parse::<u64>().ok())
234 .unwrap_or(0), 245 .unwrap_or(0),
246 started_at: state
247 .data
248 .get("started_at")
249 .and_then(|value| value.parse::<u64>().ok())
250 .unwrap_or(0),
251 commit: state.data.get("commit").cloned(),
252 seed: state.data.get("seed").cloned(),
235 error: state 253 error: state
236 .data 254 .data
237 .get("error") 255 .get("error")
@@ -242,7 +260,7 @@ impl CrashView {
242} 260}
243 261
244/// The lobby: who is logged in, the balance, and what can be played. 262/// The lobby: who is logged in, the balance, and what can be played.
245#[derive(Debug, Clone, PartialEq, Eq)] 263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
246pub struct LobbyView { 264pub struct LobbyView {
247 pub connected: bool, 265 pub connected: bool,
248 pub user: Option<String>, 266 pub user: Option<String>,
@@ -254,13 +272,14 @@ pub struct LobbyView {
254} 272}
255 273
256/// The play log, already parsed. 274/// The play log, already parsed.
257#[derive(Debug, Clone, PartialEq)] 275#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258pub struct HistoryView { 276pub struct HistoryView {
259 pub records: Vec<crate::history::PlayRecord>, 277 pub records: Vec<crate::history::PlayRecord>,
260} 278}
261 279
262/// Everything a frontend can draw. 280/// Everything a frontend can draw.
263#[derive(Debug, Clone, PartialEq)] 281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282#[serde(tag = "screen", rename_all = "snake_case")]
264pub enum View { 283pub enum View {
265 Lobby(LobbyView), 284 Lobby(LobbyView),
266 History(HistoryView), 285 History(HistoryView),
@@ -271,17 +290,28 @@ pub enum View {
271} 290}
272 291
273/// Semantic input. Frontends turn keys and clicks into these. 292/// Semantic input. Frontends turn keys and clicks into these.
274#[derive(Debug, Clone, PartialEq, Eq)] 293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
275pub enum Command { 294pub enum Command {
276 /// The credential is already hashed by the frontend. 295 /// The credential is already hashed by the frontend.
277 Login { user: String, credential: String }, 296 Login {
297 user: String,
298 credential: String,
299 },
278 Games, 300 Games,
279 Ping, 301 Ping,
280 Echo(String), 302 Echo(String),
281 Info, 303 Info,
282 Register { user: String, credential: String }, 304 Register {
283 Play { game: String, bet: Option<u64> }, 305 user: String,
284 History { limit: u32 }, 306 credential: String,
307 },
308 Play {
309 game: String,
310 bet: Option<u64>,
311 },
312 History {
313 limit: u32,
314 },
285 ShowLobby, 315 ShowLobby,
286 Logout, 316 Logout,
287 SetBet(u64), 317 SetBet(u64),