From 7317042e8d022fd196be24c9482647d56ac05e80 Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Thu, 3 Sep 2026 15:35:04 +0200 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20WS=20live=20feed=20offline=20?= =?UTF-8?q?=E2=80=94=20native=20plugin=20sends=20no=20Origin=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Login worked after the mixed-content fix, but the live feed 403'd silently: tauri-plugin-websocket's connect() runs on Tauri's Rust side, not inside the webview page, so it never auto-attaches Origin the way a browser WebSocket would — routes/ws.ts's anti-CSWSH check rejects a missing Origin before auth. platform-ws.ts now sets Origin: tauri://localhost explicitly. Also fixes a second, independent gap the above alone wouldn't have caught: komodo/resources.toml's booth Stacks had WS_ALLOWED_ORIGINS= empty in production despite .env.example documenting it as required for desktop. Needs a Komodo sync + redeploy to reach a live booth. --- apps/web/src/lib/platform-ws.ts | 11 +++++++++-- komodo/resources.toml | 10 ++++++++-- wiki/decisions/desktop-shell-tauri.md | 13 +++++++++++++ wiki/log.md | 14 ++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/web/src/lib/platform-ws.ts b/apps/web/src/lib/platform-ws.ts index 25dcc4b..fb2ee1b 100644 --- a/apps/web/src/lib/platform-ws.ts +++ b/apps/web/src/lib/platform-ws.ts @@ -62,7 +62,13 @@ class TauriSocketAdapter implements PlatformSocket { try { const { default: TauriWebSocket } = await import("@tauri-apps/plugin-websocket"); if (this.#closed) return; // close() called before connect resolved - const conn = await TauriWebSocket.connect(url); + // Runs on Tauri's native (Rust) side, NOT inside the webview page — there + // is no page context to auto-attach an Origin header the way a real + // browser WebSocket would. The server's anti-CSWSH check (routes/ws.ts) + // rejects any handshake with a missing/mismatched Origin, so it must be + // set explicitly here to match what WS_ALLOWED_ORIGINS expects + // (tauri://localhost — see apps/server/.env.example). + const conn = await TauriWebSocket.connect(url, { headers: { Origin: "tauri://localhost" } }); if (this.#closed) { void conn.disconnect(); return; @@ -78,7 +84,8 @@ class TauriSocketAdapter implements PlatformSocket { // routes/ws.ts) — nothing else is expected. }); this.onopen?.(); - } catch { + } catch (err) { + console.error("Tauri WebSocket connect failed:", url, err); this.onerror?.(); this.onclose?.(); } diff --git a/komodo/resources.toml b/komodo/resources.toml index fdbc5b0..f2ec97b 100644 --- a/komodo/resources.toml +++ b/komodo/resources.toml @@ -52,7 +52,10 @@ REGISTRY=git.infra.msai.al/mca/parking_solution TAG=stage-28bd838 COOKIE_SECURE=0 VISION_ENABLED=1 -WS_ALLOWED_ORIGINS= +# Desktop app WS handshake: Origin is tauri://localhost (set explicitly by +# platform-ws.ts, since the native WS plugin has no page context to auto-attach +# one). Linux may also send http://tauri.localhost. See routes/ws.ts anti-CSWSH check. +WS_ALLOWED_ORIGINS=tauri://localhost,http://tauri.localhost JWT_SECRET=[[park_buzi_jwt_secret]] EVENT_SIGNING_KEY=[[park_buzi_event_signing_key]] BACKUP_KEY=[[park_buzi_backup_key]] @@ -82,7 +85,10 @@ REGISTRY=git.infra.msai.al/mca/parking_solution TAG=stage-28bd838 COOKIE_SECURE=0 VISION_ENABLED=1 -WS_ALLOWED_ORIGINS= +# Desktop app WS handshake: Origin is tauri://localhost (set explicitly by +# platform-ws.ts, since the native WS plugin has no page context to auto-attach +# one). Linux may also send http://tauri.localhost. See routes/ws.ts anti-CSWSH check. +WS_ALLOWED_ORIGINS=tauri://localhost,http://tauri.localhost JWT_SECRET=[[park_2_jwt_secret]] EVENT_SIGNING_KEY=[[park_2_event_signing_key]] BACKUP_KEY=[[park_2_backup_key]] diff --git a/wiki/decisions/desktop-shell-tauri.md b/wiki/decisions/desktop-shell-tauri.md index 1fdd410..1560e3f 100644 --- a/wiki/decisions/desktop-shell-tauri.md +++ b/wiki/decisions/desktop-shell-tauri.md @@ -164,6 +164,19 @@ Per the user's choices — the operator **keeps OS access** (no fullscreen lockd - `logger.ts`'s `flushBeacon()` (page-hide `navigator.sendBeacon`) is a native browser API with no Tauri equivalent — it still drops silently in the desktop shell on unload. Accepted: the regular 4s-interval flush (now fixed, routes through `platformFetch`) covers the common case. + - **Gotcha (found immediately after shipping the above): the native WS plugin sends no `Origin` + header.** `tauri-plugin-websocket`'s `connect()` runs on Tauri's Rust side, not inside the + webview page — there's no page context to auto-attach `Origin: tauri://localhost` the way a real + browser `WebSocket` would. The server's anti-CSWSH check (`routes/ws.ts`, `isAllowedOrigin`) + treats a missing Origin as untrusted and 403s the handshake before touching auth — the live feed + showed **"JASHTË LINJË"** (offline) in the desktop app while the browser showed **"LIVE"**, same + server, same moment. **Fix (two parts, both needed):** `platform-ws.ts`'s `connect()` call now + passes `{ headers: { Origin: "tauri://localhost" } }` explicitly; separately, `komodo/ + resources.toml`'s booth Stacks had `WS_ALLOWED_ORIGINS=` **empty** in production (despite + `.env.example` documenting `tauri://localhost,http://tauri.localhost` as required) — even a + correct Origin header is useless if the server's allowlist doesn't include it. Both fixed + together; a `resources.toml` change still needs a Komodo sync + Stack redeploy to take effect on + a live booth, it isn't automatic from a git push alone. - **`VITE_API_BASE` — desktop vs. browser (regression found + fixed 2026-09-03):** `apps/web/.env.production` (committed, shared by both builds) sets `VITE_API_BASE=` (empty) — this is correct for the **browser/booth** build (Fastify same-origin, stays relative) since commit diff --git a/wiki/log.md b/wiki/log.md index 8d5f1be..42dc98f 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -2779,3 +2779,17 @@ HTTP/WS client instead of the webview's own: tauri-plugin-http (a genuine fetch( into api.ts/logger.ts via a new platformFetch() in origin.ts) and tauri-plugin-websocket (NOT a drop-in — async/listener API — adapted behind a native-WebSocket-shaped interface in the new platform-ws.ts so use-live-feed.ts needed no changes). Full detail on [[desktop-shell-tauri]]. + +## [2026-09-03] fix | Desktop live feed offline: native WS plugin sends no Origin, prod allowlist was empty + +Login worked after the mixed-content fix, but the live feed showed offline in the desktop app while +the browser showed LIVE, same server. tauri-plugin-websocket's connect() runs on Tauri's Rust side, +not inside the webview page, so it never auto-attaches an Origin header — routes/ws.ts's anti-CSWSH +check treats a missing Origin as untrusted and 403s before auth. Compounded by a second, independent +gap: komodo/resources.toml's booth Stacks had WS_ALLOWED_ORIGINS= empty in production, despite +.env.example documenting tauri://localhost as required for the desktop app. Fixed both: platform-ws.ts +now passes Origin: tauri://localhost explicitly in connect()'s headers; resources.toml's two Stacks +get the real allowlist. Needs a Komodo sync + redeploy to reach a live booth, not just a git push. +Also confirmed the "update downloads then nothing happens" report was an older pre-fix build (v0.1.2) +self-updating — expected, not a new bug; v0.1.3 carries the error-logging fix from the mixed-content +commit and should surface a real error going forward. Full detail on [[desktop-shell-tauri]].