Ctrl K
ComunidadGuías…

docs/DESKTOP.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# LobbyForge Desktop

The official native client for LobbyForge. Built with **Tauri 2** — a thin,
capability-scoped Rust shell that wraps a WebView2/WebKitGTK/WKWebView window
around a self-hosted LobbyForge instance.

## Connect-to-server model

The desktop app does **not** bundle the web app. LobbyForge's web app is a
Next.js server (SSR + API routes + Postgres + Redis + LiveKit) that runs on
your own server. The desktop client is a native window that loads your
instance URL inside a sandboxed webview and adds native capabilities the
browser can't provide.

**One official installer serves every instance.** On first launch the client
asks for the instance URL and remembers it. There is no per-instance branding
or build step — the same `.msi` / `.dmg` / `.deb` works for everyone.

```
┌──────────────────────────┐        ┌─────────────────────────────┐
│  Desktop client (Tauri)  │        │  Your LobbyForge instance   │
│                          │        │  (Next.js + PG + Redis +    │
│  ┌────────────────────┐  │ HTTPS  │  LiveKit on your server)    │
│  │  WebView →─────────┼──┼────────►│                             │
│  │  instance URL      │  │        │  /lobby  /admin  /api/...   │
│  └────────────────────┘  │        │                             │
│  + global PTT shortcut   │        └─────────────────────────────┘
│  + tray + notifications  │
│  + single instance       │
└──────────────────────────┘
```

## Download

Pre-built installers are attached to every LobbyForge **GitHub Release**
(`v*` tags), next to the server image, with SHA-256 checksums. They are built
by `desktop-release.yml`, which `release.yml` calls:

- **Windows**: `desktop-windows-LobbyForge_x.y.z_x64-setup.exe`. One click,
  no admin prompt: installs for the current user in `%LOCALAPPDATA%\LobbyForge`
  and opens the app. An `.msi` (per machine, admin) is added for versions
  without a pre-release suffix.
- **macOS**: `.dmg`
- **Linux**: `.deb`, `.rpm` or `.AppImage`

Code signing turns on when the signing secrets are configured; until then
Windows SmartScreen and macOS Gatekeeper warn on first run. See
[DESKTOP_SIGNING.md](DESKTOP_SIGNING.md) for the installer's switches and
the signing setup.

## First launch

1. Install the client.
2. On the connect screen, enter your instance URL (e.g.
   `https://lobby.example.com`). HTTPS is required; loopback `http://localhost`
   is permitted only in debug builds for local development.
3. Click **Connect**. The instance loads in the window and the URL is saved
   for next launch.
4. To switch instances later, open the tray menu and choose **Switch
   instance…**. It forgets the saved address and brings back the connect
   screen from any page, including an instance that no longer loads (the
   app cannot tell a failed load from a slow one, so it does not fall back
   on its own).

The connect screen, its error messages and the tray menu follow the
system's language when the instance ships it (`apps/web/messages/<code>/desktop.json`),
otherwise English. The Windows installer offers the same languages, as far
as Tauri's installer translations go. Adding a language needs no code; see
[TRANSLATING.md](TRANSLATING.md#the-desktop-app).

## Native capabilities

| Capability | Behavior |
|------------|----------|
| **Global push-to-talk** (opt-in) | Off by default. Tick **Push to talk anywhere (Ctrl+Space)** in the tray menu, then hold `Ctrl+Space` anywhere (even when the client isn't focused) to talk; the client forwards the key state to the instance via a `lobbyforge:ptt` event. The choice is saved (`globalPushToTalk` in the app's `settings.json`). A global shortcut takes the key away from every other app — `Ctrl+Space` switches the input language on many systems — so it is registered only while an instance is shown and released on disconnect. Push-to-talk inside the focused window works without it (the instance's own key binds). |
| **Other global shortcuts** (off) | `Ctrl+Shift+M` (mute), `Ctrl+Shift+D` (deafen) and `Ctrl+,` (settings) are registered system-wide only when `globalShortcuts` is `true` in `settings.json`; there is no menu item for them. |
| **Tray icon** | Hide the window to the system tray; menu: Show / Switch instance… / Push to talk anywhere / Quit. |
| **Single instance** | A second launch focuses the existing window instead of opening a duplicate. |
| **Persisted instance** | The last-connected URL is restored automatically on launch; **Switch instance…** in the tray forgets it. |
| **Deep-link auth** (infrastructure) | `lobbyforge://session/complete?code=&state=&instance=` handler for the OAuth session handoff flow. |

## Build from source

### Prerequisites

- **Rust** (stable, MSVC toolchain on Windows): `rustup default stable-x86_64-pc-windows-msvc`
- **Windows**: Microsoft C++ Build Tools (MSVC) + Windows 11 SDK (via Visual Studio Installer → "Desktop development with C++")
- **Linux**: `libwebkit2gtk-4.1-dev librsvg2-dev patchelf`
- **macOS**: Xcode Command Line Tools
- **Node.js ≥ 22**, **pnpm ≥ 10.12.1**

### Develop

```sh
pnpm install
pnpm --filter @lobbyforge/desktop dev   # opens the window (tauri dev)
```

### Produce an installer

```sh
pnpm --filter @lobbyforge/desktop build  # tsc + tauri build
# → apps/desktop/src-tauri/target/release/bundle/
```

### Publish a release

Desktop installers ship with the unified release: pushing a `v*` tag runs
`release.yml`, which calls `desktop-release.yml` to build (and, when
configured, sign) all three platforms with the tag's version, then attaches
them to the GitHub Release. `desktop-v*` tags are no longer used.

For a test build, run **Actions → desktop-release → Run workflow**: the
installers are attached to the run, and a Windows smoke test installs,
launches, reinstalls and uninstalls the NSIS build.

## Security

- The instance webview is **isolated per origin** — it gets no shell,
  filesystem, process, or updater capability. Only the audited desktop event
  bridge (PTT forwarding) crosses the boundary.
- Instance URL validation enforces HTTPS origins only (loopback http in debug
  builds). Credentials, query strings, and fragments are rejected.
- Deep-link auth codes are never logged — the shell redacts `code=` before
  writing to logs (see `redactDesktopHandoff` in `src/index.ts`).
- **Navigation is pinned** (security-review DESK-V-01). The window has no
  address bar, so it only ever shows the connected instance (its exact
  origin) and the bundled connect screen. Any other `http(s)` page — a link,
  a redirect, a script navigating the window — opens in the system browser
  instead (at most one a second); other schemes (`file:`, `data:`, custom
  protocols) are dropped; `window.open` / `target=_blank` never creates a
  second app window. The decision is `decide_top_level_navigation` in
  `src-tauri/src/lib.rs`. On macOS and Linux the webview asks about iframe
  navigations too, so embeds the instance frames on purpose (Watch Party's
  YouTube player, a CAPTCHA widget) load, and a top-level page outside the
  instance is caught on page load and replaced by the instance. One
  consequence: sign-in pages of other sites (e.g. "Continue with Google")
  open in the system browser — sign in with the instance's own form in the
  app.
- **The browser-login handoff goes to its instance only** (DESK-V-02). The
  deep link's code + state are posted to the origin the login was started
  for, never broadcast (`'*'`), and connecting to another instance or
  disconnecting drops a login that is still pending.

## Layout

```
apps/desktop/
  src/              # TS contracts (handoff validators, shortcut maps) + tests
  src-tauri/        # Rust native shell (Tauri 2)
    Cargo.toml
    tauri.conf.json
    src/{lib.rs, main.rs}
    capabilities/   # permission scopes
    icons/          # generated icon set (pnpm tauri icon)
  dist-shell/       # connect-screen frontend (static HTML/JS)
```