fix(desktop): route fetch + WebSocket through native Tauri plugins (mixed-content)
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:
@@ -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
@@ -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]].
|
||||
|
||||
Reference in New Issue
Block a user