Files
openchamber/packages/ui/src/lib/relay/runtime-tunnel.ts
T
Bohdan Triapitsyn 9b645f0e53 perf: fast relay connect on mobile and desktop + connect splash + edit-safe instances
Relay connect used to serialize a dead LAN probe (up to 8s per stale address
on mobile, 2-4s on desktop) in front of the relay attempt, then paid a second
WebSocket connect + E2EE handshake because the probe tunnel was thrown away.

- mobile probeConnectionCandidates: race the relay probe against the direct
  chain with a 1.5s direct headstart; a live LAN still wins, a dead one no
  longer delays startup
- relay probes adopt their tunnel as the runtime tunnel (adoptRelayTunnel)
  instead of dialing a fresh one — applies to auto-connect, pairing redeem,
  password login, and the desktop host switcher's relay fallback
- relay probe drops the /health round-trip: the E2EE handshake already proves
  the server identity, /auth/session alone proves liveness and auth
- desktop restoreDesktopRelayRuntime: same headstart race; a late direct
  success hot-switches back (stable runtimeKey); startup probe now passes
  expectedServerId so a re-leased LAN address never sees the token
- launch splash shows 'Connecting to device: <label>' with animated dots
  under the (still centered) logo, translated in all locales
- editing a saved instance no longer rebuilds it from the URL field alone:
  the id is passed through, relay/https candidates are preserved, and a
  token-key change migrates the Keychain token instead of orphaning it
2026-07-13 01:29:35 +03:00

61 lines
2.2 KiB
TypeScript

// Module-level singleton holding the active relay tunnel client, if the runtime
// is in relay mode. Kept in its own module so runtime-switch, runtime-fetch,
// runtime-url, and the event pipeline can all read it without an import cycle
// (runtime-switch <-> runtime-url).
import { createRelayTunnelClient, type RelayTunnelClient } from './tunnel-client';
export interface RelayRuntimeDescriptor {
relayUrl: string;
serverId: string;
hostEncPubJwk: JsonWebKey;
grant?: string;
}
let activeTunnel: RelayTunnelClient | null = null;
let activeDescriptor: RelayRuntimeDescriptor | null = null;
const descriptorsEqual = (a: RelayRuntimeDescriptor, b: RelayRuntimeDescriptor): boolean =>
a.relayUrl === b.relayUrl &&
a.serverId === b.serverId &&
a.grant === b.grant &&
JSON.stringify(a.hostEncPubJwk) === JSON.stringify(b.hostEncPubJwk);
export const getActiveRelayTunnel = (): RelayTunnelClient | null => activeTunnel;
export const isRelayModeActive = (): boolean => activeTunnel !== null;
/**
* Activates relay mode with the given descriptor, replacing any previous tunnel.
* Reuses the existing client when the descriptor is unchanged so a redundant
* runtime switch does not tear down a live tunnel.
*/
export const activateRelayTunnel = (descriptor: RelayRuntimeDescriptor): RelayTunnelClient => {
if (activeTunnel && activeDescriptor && descriptorsEqual(activeDescriptor, descriptor)) {
return activeTunnel;
}
activeTunnel?.close();
activeDescriptor = descriptor;
activeTunnel = createRelayTunnelClient(descriptor);
return activeTunnel;
};
/**
* Adopts an ALREADY-OPEN tunnel client (e.g. the connect flow's probe tunnel)
* as the active runtime tunnel, so the immediately following
* `activateRelayTunnel` with an equal descriptor reuses it instead of paying a
* second WebSocket connect + E2EE handshake. Replaces any previous tunnel.
*/
export const adoptRelayTunnel = (descriptor: RelayRuntimeDescriptor, client: RelayTunnelClient): void => {
if (activeTunnel === client) return;
activeTunnel?.close();
activeDescriptor = descriptor;
activeTunnel = client;
};
export const deactivateRelayTunnel = (): void => {
activeTunnel?.close();
activeTunnel = null;
activeDescriptor = null;
};