# Desktop bridge (JS to native), FROZEN The desktop app is the same web client running in a Tauri v2 webview. Everything the browser cannot do (raw UDP, Opus encode/decode without WebCodecs, OS keyring, global push-to-talk) is exposed by Rust as a small, narrow API. The web client detects it and uses it; a plain browser never sees it. Detection: ```js const native = globalThis.__veiledNative ?? null; // injected by the Tauri shell if (native) { /* desktop path: UDP media, OS keyring */ } ``` Capabilities are advertised, never assumed, because Windows and Linux builds may differ: ```js native.caps() // -> { udp: true, opus: true, keyring: true, ptt: false, videoHw: true } ``` ## API | Method | Returns | Notes | | --- | --- | --- | | `caps()` | object | as above | | `udpBind(host, port, bindTokenHex)` | `{ok, localPort}` | opens the UDP socket, sends `MEDIA_BIND` (kind 5), starts the receive loop | | `udpSend(bytes)` | `{ok}` | one envelope frame; returns immediately, never blocks the UI thread | | `udpClose()` | `{ok}` | | | `onMediaFrame(cb)` | unsubscribe | `cb(Uint8Array)` per received envelope, already stripped of any transport tag | | `onSocketState(cb)` | unsubscribe | `cb({state, boundAt, lost, rttMs})` where state is `unbound`, `bound`, `stale` | | `opusEncode(pcm, {bitrate, frameMs})` | `Promise` | PCM f32 mono 48 kHz in, one Opus frame out | | `opusDecode(frame)` | `Promise` | returns 480 samples for a 10 ms frame, or a concealment frame on loss | | `opusConfig(bitrate, frameMs)` | `{ok}` | applied to both directions | | `identityLoad()` | `Promise` | from the OS keyring; falls back to a 600-mode file | | `identitySave(json)` | `Promise<{ok}>` | | | `pttEnable(accel)` | `{ok}` | global shortcut, fired as `onPtt(cb)` with `true`/`false` | | `hwVideoSupport()` | `{h264, vp9, av1}` | decides the screen-share codec | Rules for both sides: 1. **The bridge never carries keys to disk in the clear.** `identitySave` writes to the OS keyring (Windows Credential Manager, libsecret on Linux) and only falls back to a file when the keyring is unavailable, at which point it says so in the UI. 2. **The bridge never parses the protocol.** It moves bytes. All framing, crypto and routing stays in the shared TypeScript so desktop and web cannot drift. 3. **Every call is non-blocking from the renderer's point of view.** Opus work runs on a Tokio blocking thread and returns a promise; a 10 ms frame must never cost a dropped animation frame in the message list. 4. **Failure is reported, never silent.** A UDP bind failure surfaces as `state: "unbound"` plus a reason the UI shows, and the client falls back to WebSocket media so voice still works through any proxy or tunnel.