diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-08 03:29:35 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-08 03:29:35 +0100 |
| commit | 058432130628d00764663ecf819a9adc6793768d (patch) | |
| tree | d3f49f111fbfaa48647cca2600adcf9017af2d30 /web/play/src/history.ts | |
| parent | web: serve latest.php through PHP and refuse stray PHP files (diff) | |
| download | zagan-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 'web/play/src/history.ts')
| -rw-r--r-- | web/play/src/history.ts | 60 |
1 files changed, 60 insertions, 0 deletions
diff --git a/web/play/src/history.ts b/web/play/src/history.ts new file mode 100644 index 0000000..9e7a564 --- /dev/null +++ b/web/play/src/history.ts | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | import type { View } from "./types"; | ||
| 2 | import { clear, formatTime, money, node, signedMoney } from "./dom"; | ||
| 3 | |||
| 4 | function summary(record: any): string { | ||
| 5 | const details = record.details ?? {}; | ||
| 6 | switch (record.game) { | ||
| 7 | case "slots": | ||
| 8 | return String(details.win_type ?? "spin"); | ||
| 9 | case "roulette": | ||
| 10 | return `${details.number ?? "?"} ${details.color ?? ""}`.trim(); | ||
| 11 | case "blackjack": | ||
| 12 | return String(details.result ?? ""); | ||
| 13 | case "crash": { | ||
| 14 | const result = String(details.result ?? ""); | ||
| 15 | const point = details.crash_point; | ||
| 16 | return point === undefined || point === null ? result : `${result} @ ${Number(point).toFixed(2)}×`; | ||
| 17 | } | ||
| 18 | default: | ||
| 19 | return ""; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | export function renderHistory(root: HTMLElement, view: View): void { | ||
| 24 | clear(root); | ||
| 25 | const records: any[] = view.records ?? []; | ||
| 26 | if (records.length === 0) { | ||
| 27 | const empty = node("div", "board"); | ||
| 28 | empty.append( | ||
| 29 | node("h2", undefined, "No plays yet"), | ||
| 30 | node("p", "muted", "Your settled plays will appear here."), | ||
| 31 | ); | ||
| 32 | root.append(empty); | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | const wrap = node("div", "table-wrap"); | ||
| 37 | const table = node("table"); | ||
| 38 | const head = node("thead"); | ||
| 39 | const headRow = node("tr"); | ||
| 40 | for (const label of ["Time", "Game", "Bet", "Result", "Net", "Balance"]) { | ||
| 41 | headRow.append(node("th", undefined, label)); | ||
| 42 | } | ||
| 43 | head.append(headRow); | ||
| 44 | const body = node("tbody"); | ||
| 45 | for (const record of records) { | ||
| 46 | const row = node("tr"); | ||
| 47 | row.append(node("td", "mono", formatTime(String(record.at ?? "")))); | ||
| 48 | row.append(node("td", undefined, String(record.game ?? ""))); | ||
| 49 | row.append(node("td", "num", money(Number(record.bet ?? 0)))); | ||
| 50 | row.append(node("td", "muted", summary(record))); | ||
| 51 | const net = Number(record.winnings ?? 0) - Number(record.bet ?? 0); | ||
| 52 | const netCell = node("td", `num ${net > 0 ? "pos" : net < 0 ? "neg" : ""}`, net === 0 ? "0 Kr" : signedMoney(net)); | ||
| 53 | row.append(netCell); | ||
| 54 | row.append(node("td", "num", money(Number(record.balance_after ?? 0)))); | ||
| 55 | body.append(row); | ||
| 56 | } | ||
| 57 | table.append(head, body); | ||
| 58 | wrap.append(table); | ||
| 59 | root.append(wrap); | ||
| 60 | } | ||
