Files
julian 8fa66c9911
Build & push images / images (push) Successful in 2m51s
Release desktop / bundle (push) Successful in 41m19s
fix(desktop): WS ticket auth for the live feed; desktop logs never reached the server
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
2026-09-04 12:09:30 +02:00
..

@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

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.