fix(desktop): route fetch + WebSocket through native Tauri plugins (mixed-content)
Build desktop / desktop (push) Successful in 4m33s
Build & push images / images (push) Successful in 2m50s
CI / check (push) Successful in 43s
Release desktop / bundle (push) Successful in 5m13s

Fixing VITE_API_BASE got login to build a correct absolute URL, but it still
failed with WebKit's generic "Load failed" — WebKitGTK treats tauri://localhost
as a secure origin, so http://127.0.0.1:3000 (and ws://) from inside it is
blocked as mixed content, a WebKit limitation CSP's connect-src can't override.

Added tauri-plugin-http (genuine fetch() drop-in, wired via a new
platformFetch() in origin.ts, used by api.ts + logger.ts) and
tauri-plugin-websocket (not a drop-in — adapted behind a native-WebSocket-
shaped interface in the new platform-ws.ts so use-live-feed.ts needed no
changes). Both route through Tauri's Rust side instead of the webview's own
fetch/WebSocket. Capabilities scoped to 127.0.0.1:3000/localhost:3000, matching
the existing CSP allowlist.
This commit is contained in:
2026-09-03 14:57:45 +02:00
parent 276b048fa9
commit 439b11d16d
13 changed files with 776 additions and 16 deletions
+24
View File
@@ -140,6 +140,30 @@ Per the user's choices — the operator **keeps OS access** (no fullscreen lockd
- **Right-click:** the context menu is blocked in **prod only** (`apps/web/src/lib/kiosk.ts`,
guarded on `import.meta.env.PROD`); dev keeps right-click + devtools. Applies to both the browser
prod build and the desktop build (same SPA).
- **Mixed content blocks http(s)/ws(s) from the webview — fixed 2026-09-03.** Even with
`VITE_API_BASE` correctly set (below), login still failed with WebKit's generic `"Load failed"`.
Root cause is a separate, deeper issue: WebKitGTK treats `tauri://localhost` as a **secure
origin**, so a plain `http://127.0.0.1:3000` `fetch()` — or a `ws://127.0.0.1:3000` WebSocket —
from inside it is blocked as **mixed content**, a long-standing WebKit limitation
([bugs.webkit.org #171934](https://bugs.webkit.org/show_bug.cgi?id=171934)). `connect-src` in the
CSP does **not** override this — it's a different browser security layer entirely, so the request
never even reaches the network layer to be diagnosable via server logs. **Fix:** two Tauri plugins
route the SPA's traffic through Tauri's native (Rust) side instead of the webview's own
fetch/WebSocket, which sidesteps the check entirely:
- **`tauri-plugin-http`** — `apps/web/src/lib/origin.ts`'s `platformFetch()` dynamically imports
`@tauri-apps/plugin-http`'s `fetch` (a genuine drop-in for the standard Fetch API) inside Tauri,
plain `fetch` in the browser. `api.ts` and `logger.ts` both call `platformFetch` instead of the
global `fetch` now.
- **`tauri-plugin-websocket`** — NOT a drop-in (async `connect()`/listener-callback API, not
`onopen`/`onmessage`/sync `send`/`close`). `apps/web/src/lib/platform-ws.ts` adapts it behind
the same native-`WebSocket`-shaped interface `use-live-feed.ts` already expects (hardened for
reconnect backoff + StrictMode double-invoke), so that hook needed zero changes.
- Capability grants: `apps/desktop/src-tauri/capabilities/default.json` adds `websocket:default`
and a scoped `http:default` (`allow: [{url: "http://127.0.0.1:3000"}, {url:
"http://localhost:3000"}]`) — deny-by-default, matching the CSP's existing allowlist.
- `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.
- **`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
+12
View File
@@ -2767,3 +2767,15 @@ zero diagnostic trail. Fixed release.yml to sed-patch tauri.conf.json's version
right before building (checked-in value is now dev-only, never hand-maintained for releases), and
split desktop-updater.ts's catch so a real post-accept failure logs instead of vanishing. Full
detail on [[desktop-shell-tauri]].
## [2026-09-03] fix | Desktop login "Load failed": WebKit mixed-content, not CORS/CSP
After fixing VITE_API_BASE, login still failed with WebKit's generic "Load failed" — a raw browser
fetch() rejection with no server-side trace, since the request never reached the network. Root
cause: WebKitGTK treats tauri://localhost as a secure origin, so http://127.0.0.1:3000 (and
ws://127.0.0.1:3000) from inside it is blocked as mixed content — a known WebKit limitation, NOT
fixable via CSP connect-src. Fixed by routing both through Tauri plugins that use the native (Rust)
HTTP/WS client instead of the webview's own: tauri-plugin-http (a genuine fetch() drop-in, wired
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]].