block / block/buzz

Windows desktop cannot join a self-hosted relay: `http://tauri.localhost` missing from CORS guidance, fails with a silent "Failed to fetch"

Open
#3,490 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

# Windows desktop cannot join a self-hosted relay: `http://tauri.localhost` missing from CORS guidance, fails with a silent "Failed to fetch"

## Summary

On Windows, Buzz Desktop's WebView2 origin is `http://tauri.localhost`. On macOS/Linux it is `tauri://localhost`. A self-hosted relay whose `BUZZ_CORS_ORIGINS` was built from the shipped `.env.example` (which lists **no** Tauri origin at all) or from a macOS-based walkthrough will silently reject every request the Windows desktop makes during "Join a community".

The only user-visible symptom is a faint **"Failed to fetch"** on the join screen. Nothing is written to the relay log — the request never gets past the browser's CORS check — and nothing is persisted client-side, so the app loops back to onboarding with no diagnostic. It looks identical to a network or auth failure, which sends self-hosters down the wrong path (membership gates, auth tokens, firewall, `ws://` vs `wss://`) for hours.

This is likely a contributing factor in several open self-hosting reports, e.g. #3088, #2816, #2662.

## Environment

- Relay: `ghcr.io/block/buzz:main` (image `75b532832ca1`), `deploy/compose` bundle, plain HTTP on `:3000` over Tailscale (no TLS proxy)
- Desktop: Buzz Desktop **0.4.26**, Windows 11
- Relay config: `BUZZ_REQUIRE_RELAY_MEMBERSHIP=false`, `BUZZ_REQUIRE_AUTH_TOKEN=false` (fully open relay)

## Reproduction

1. Deploy via `deploy/compose` with `BUZZ_CORS_ORIGINS=http://:3000,tauri://localhost,https://tauri.localhost` — i.e. the two origins a macOS/Linux guide would give you.
2. On Windows, open Buzz Desktop → "Join a community" → enter `ws://:3000`.
3. Join fails with "Failed to fetch". No community is persisted.

## Evidence

Preflight against the relay, same host, differing only in `Origin`:

```
$ curl -s -i -X OPTIONS -H "Origin: tauri://localhost" \
-H "Access-Control-Request-Method: GET" http://:3000/health
HTTP/1.1 200 OK
access-control-allow-origin: tauri://localhost <-- allowed

$ curl -s -i -X OPTIONS -H "Origin: http://tauri.localhost" \
-H "Access-Control-Request-Method: GET" http://:3000/health
HTTP/1.1 200 OK
<-- no ACAO header, blocked
```

That the Windows origin really is `http://tauri.localhost` is confirmed by the desktop's own storage — every key in
`%LOCALAPPDATA%\xyz.block.buzz.app\EBWebView\Default\Local Storage\leveldb` is namespaced under `_http://tauri.localhost`:

```
_http://tauri.localhost buzz-theme-cache {...}
_http://tauri.localhost buzz.machine-onboarding.identity-key-help-seen.v1 true
_http://tauri.localhost buzz-machine-onboarding-complete.v2: true
```

Note there is no `buzz-communities` key: the join never persists anything, so the app cannot retry or show the community in a broken state.

Relay-side, a machine in this condition produces **zero** entries in `docker compose logs relay` — the connection is never attempted, which strongly misleads diagnosis toward network/firewall causes.

The codebase already knows both origins exist — `desktop/src-tauri/src/media_proxy.rs:38` checks for both:

```rust
if !origin.is_empty() && origin != "tauri://localhost" && origin != "http://tauri.localhost" {
```

but the relay-side guidance does not:

- `deploy/compose/.env.example` — `BUZZ_CORS_ORIGINS=https://buzz.example.com`, no Tauri origin at all
- `crates/buzz-relay/src/config.rs:88` — doc example is `"tauri://localhost,http://localhost:3000"`, omitting `http://tauri.localhost`

So any operator following the shipped example produces a config that works on macOS/Linux and is broken on Windows.

## Workaround

Add the Windows WebView2 origin to the relay allowlist and restart:

```
# deploy/compose/.env
BUZZ_CORS_ORIGINS=http://:3000,tauri://localhost,https://tauri.localhost,http://tauri.localhost

$ ./run.sh restart
```

Verify:

```
$ curl -s -i -X OPTIONS -H "Origin: http://tauri.localhost" \
-H "Access-Control-Request-Method: GET" http://:3000/health | grep -i allow-origin
access-control-allow-origin: http://tauri.localhost
```

The Windows desktop joins immediately afterwards.

## Suggested fix

1. `deploy/compose/.env.example`: ship all desktop origins by default —
`BUZZ_CORS_ORIGINS=https://buzz.example.com,tauri://localhost,http://tauri.localhost,https://tauri.localhost`
2. `crates/buzz-relay/src/config.rs:88`: include `http://tauri.localhost` in the doc example.
3. Consider always allowing the two fixed Tauri desktop origins regardless of `BUZZ_CORS_ORIGINS` — they are constants of the first-party client, not deployment-specific, and every self-hoster needs them.
4. Surface the failure: the join screen should distinguish a CORS/preflight rejection from a network error. "Failed to fetch" with no host, status, or hint is the single biggest time sink here — even echoing the attempted URL and the browser error would help.
5. Document a self-hosting/LAN page covering the required env vars (relates to #3088).

Happy to send a PR for 1 and 2 if useful.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.