feat: pairing v2 — one-tap trusted devices over LAN and private relay (#2103)
Reworks how devices connect to an OpenChamber server, end to end. Pairing v2: - One-time pairing links/QR codes (openchamber://connect?v=2) carrying a set of transport candidates (LAN/tunnel/relay) and a single-use secret redeemed server-side; no tokens embedded in links - Add-a-device dialog written for first-time users: intent-based transport choice (Anywhere / Home network only / This computer only) with plain-language descriptions, transparent fallback checkboxes, server-authoritative LAN detection, high-res QR dialog - Private relay folded into pairing as a transport candidate with a demand-driven lifecycle (enables when a relay device is paired, disables when none remain) Multi-transport devices: - A saved device holds all its transports and one token; mobile re-probes on connect, resume, and network change and hot-switches LAN<->relay seamlessly (no re-pairing, no remount, session preserved) - Desktop can import relay pairing links, switch to relay hosts through the E2EE tunnel, and restore a relay default host after relaunch Device management: - Device list (web + desktop) shows live per-device connectivity with the active transport (Connected - Local network / Relay) and platform badges (iOS/Android/macOS/Windows/Linux) - One physical device = one record: stable per-install dedupe keys across pairing and password re-login; typed pairing label names the device, paired devices name the connection by the issuing server hostname - Trusted desktop-local client manages all devices (list, revoke, clear revoked); relay host reaps dead client sockets after 3 missed keepalives Android: - LAN transport unblocked (cleartext + mixed content, mirroring iOS ATS exceptions); resume re-probe retries through network flux and silently auto-reconnects from a disconnected state
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { isElectronShell } from '@/lib/desktop';
|
||||
import { desktopHostsGet } from '@/lib/desktopHosts';
|
||||
import { getRuntimeKey, switchRuntimeEndpoint } from '@/lib/runtime-switch';
|
||||
|
||||
/**
|
||||
* On desktop startup, re-open the E2EE relay tunnel if the default host is a
|
||||
* relay host. Relay hosts have no reachable HTTP base, so the Electron shell
|
||||
* boots the LOCAL UI and defers reconnection to the renderer: here we read the
|
||||
* persisted relay descriptor + client token and activate the tunnel in-process
|
||||
* via switchRuntimeEndpoint({ relay }). Direct hosts don't need this — the shell
|
||||
* injects their apiBaseUrl/token as window globals before render.
|
||||
*
|
||||
* Safe to call unconditionally; it is a no-op outside the Electron shell and when
|
||||
* the default host is local or already active.
|
||||
*/
|
||||
export const restoreDesktopRelayRuntime = async (): Promise<void> => {
|
||||
if (!isElectronShell()) return;
|
||||
const config = await desktopHostsGet().catch(() => null);
|
||||
const defaultHostId = config?.defaultHostId;
|
||||
if (!config || !defaultHostId || defaultHostId === 'local') return;
|
||||
const host = config.hosts.find((entry) => entry.id === defaultHostId);
|
||||
if (!host?.relay) return;
|
||||
// Must match runtimeKeyForHost() in DesktopHostSwitcher so switch/resolve agree.
|
||||
const runtimeKey = `host:${host.id}`;
|
||||
if (getRuntimeKey() === runtimeKey) return;
|
||||
switchRuntimeEndpoint({
|
||||
apiBaseUrl: typeof window !== 'undefined' ? window.location.origin : '',
|
||||
clientToken: host.clientToken || null,
|
||||
runtimeKey,
|
||||
relay: host.relay,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user