8fa66c9911
The v0.1.4 Origin fix cleared only the first of two gates in /api/ws's preHandler. The second, req.jwtVerify(), reads the HttpOnly cookie — which tauri-plugin-websocket (a bare tungstenite client, no cookie jar) can never send. Every desktop handshake 401'd and use-live-feed reconnected every 10s (confirmed in the park-2 server log). - routes/ws.ts: POST /api/ws/ticket (cookie + CSRF auth) mints a 30s, single-use, in-memory ticket; the WS preHandler accepts it via an x-ws-ticket header after the Origin check, then the same report:read role check. Browser cookie path unchanged; JWT stays out of JS. - platform-ws.ts: fetch a ticket before connect, send it with the Origin header; connect failures now go through logClient (rate-limited). - logger.ts: flush read the CSRF token from document.cookie, null on desktop, so every desktop POST /api/logs 403'd and was dropped silently — no desktop client log had ever reached app_logs. Stash moved to a dependency-free lib/desktop-csrf.ts shared by api.ts and logger.ts. - backend-config.ts: ConnectScreen probe uses the unauthenticated /health (now also returns app: "parking-system") instead of accepting any 401. - README: local-AppImage release gate — tauri dev runs at http://localhost:5173, not tauri://localhost, so none of these origin-dependent bugs reproduce there. - wiki: new section + log entry; four citation corrections. Requires the server image with this commit deployed before the new desktop build connects (the ticket endpoint must exist). Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
72 lines
4.0 KiB
Markdown
72 lines
4.0 KiB
Markdown
# @parking/desktop — Tauri v2 kiosk shell
|
|
|
|
A **thin native desktop window** over the `@parking/web` SPA. It contains **no UI and no business
|
|
logic** of its own: the window renders the *same* web app the browser does, so the desktop and the
|
|
browser stay identical and never drift. Device/auth/ledger logic stays in `@parking/server`. See
|
|
`wiki/decisions/desktop-shell-tauri.md`.
|
|
|
|
## How the "same look & functionality" guarantee works
|
|
|
|
| | Source of the UI |
|
|
| --- | --- |
|
|
| **Dev** (`tauri dev`) | the window loads `http://localhost:5173` — the **`@parking/web` Vite dev server**. Edit a component in `apps/web` → HMR updates the desktop window live. |
|
|
| **Prod** (`tauri build`) | the window bundles `apps/web`'s built `dist/`. `beforeBuildCommand` rebuilds the SPA first. |
|
|
|
|
There is only one UI codebase (`apps/web`); this package just wraps it.
|
|
|
|
## Backend connection
|
|
|
|
The SPA talks to Fastify over HTTP/WS. In a browser that's same-origin (relative `/api`). In the
|
|
desktop build the bundled assets load from `tauri://localhost`, so set **`VITE_API_BASE`** (read at
|
|
web build time — see `.env.example`) to the appliance's Fastify origin, e.g.
|
|
`http://127.0.0.1:3000`. The CSP `connect-src` in `tauri.conf.json` is already allowed for that
|
|
origin, and the backend must include the Tauri origin in `WS_ALLOWED_ORIGINS` for the live feed.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
pnpm --filter @parking/desktop dev # native window over the web dev server (HMR)
|
|
pnpm --filter @parking/desktop bundle # build the SPA + bundle the desktop app (.deb/.rpm/.AppImage)
|
|
```
|
|
|
|
> `build` is a **no-op** in this package so `turbo run build` stays fast — the real desktop bundle
|
|
> (compiles Rust, minutes long) is the explicit `bundle` script above.
|
|
|
|
Requires the Rust toolchain and (on Linux) WebKitGTK 4.1 + libsoup-3 dev libraries. Under WSL2 the
|
|
window needs a display (WSLg or an X server).
|
|
|
|
## Auto-update
|
|
|
|
Signed updates are built and published by `.gitea/workflows/release.yml` on a `vX.Y.Z` tag, mirrored
|
|
to the public `mca/public_releases` repo (this repo is private; the updater runs on offline-first
|
|
field appliances with no Gitea credentials, so its endpoint must be reachable unauthenticated —
|
|
see that workflow's header and `wiki/decisions/desktop-shell-tauri.md`). The updater config and
|
|
signing pubkey live in `tauri.conf.json`; the private signing key is held outside the repo, never
|
|
committed.
|
|
|
|
## Release gate — run the REAL bundle locally before tagging
|
|
|
|
`tauri dev` loads the SPA from `http://localhost:5173`, a plain http origin. The shipped bundle
|
|
loads it from `tauri://localhost`, a *secure* custom-scheme origin — and every desktop-only bug
|
|
found in the field on 2026-09-03/04 (relative-URL DOMException, mixed content, missing WS
|
|
`Origin`, the reqwest-vs-webview cookie split, the WS handshake that can't carry the cookie)
|
|
depends on that difference. **Dev mode cannot reproduce any of them**, so "works in `tauri dev`"
|
|
carries no information about a release. Before pushing a `vX.Y.Z` tag:
|
|
|
|
1. `pnpm --filter @parking/server dev` (local backend; `.env` must have `COOKIE_SECURE=0` and
|
|
`tauri://localhost` in `WS_ALLOWED_ORIGINS`).
|
|
2. `pnpm --filter @parking/desktop bundle` and run the produced AppImage from
|
|
`src-tauri/target/release/bundle/appimage/` (WSLg is enough).
|
|
3. On the ConnectScreen enter `127.0.0.1:3000`, **Test** must say reachable, then **Save**.
|
|
4. Log in. The booth header must show **LIVE** (not "JASHTË LINJË") within a few seconds.
|
|
5. Perform one mutation (e.g. change your UI language) — it must succeed (proves CSRF).
|
|
6. Open Setup → Logs and confirm a `frontend`-sourced row from this desktop session exists
|
|
(proves the desktop log channel; historically it was silently 403'd).
|
|
|
|
Only then tag. If a release still fails in the field, the gap is in this list — fix the list.
|
|
|
|
## Not here (deliberately)
|
|
|
|
Kiosk lockdown (fullscreen/no-decorations) and launching Fastify from the shell are out of scope for
|
|
the scaffold — on the appliance Fastify runs as its own service and this shell connects to it.
|