Relay pairing offer v2 (E2EE) + host connections

This commit is contained in:
Mohamed Boudra
2026-02-04 22:53:00 +07:00
parent 9ce4ad9758
commit 0218729316
59 changed files with 2022 additions and 1547 deletions
@@ -4,38 +4,63 @@ import {
type HostProfile,
} from "@/contexts/daemon-registry-context";
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
import { buildDaemonWebSocketUrl, buildRelayWebSocketUrl, extractHostPortFromWebSocketUrl } from "@/utils/daemon-endpoints";
import {
buildDaemonWebSocketUrl,
buildRelayWebSocketUrl,
} from "@/utils/daemon-endpoints";
import { probeDaemonEndpoint } from "@/utils/test-daemon-connection";
import { useEffect, useMemo, useRef, useState } from "react";
import type { ActiveConnection } from "@/contexts/daemon-connections-context";
function buildCandidateUrls(daemon: HostProfile): string[] {
const endpoints = daemon.endpoints ?? [];
const sessionId = daemon.relay?.sessionId ?? null;
const relayEndpoint = daemon.relay?.endpoint ?? null;
const lastKnownGood =
typeof daemon.metadata?.lastKnownGoodEndpoint === "string"
? (daemon.metadata.lastKnownGoodEndpoint as string)
: null;
type Candidate = {
connectionId: string;
url: string;
activeConnection: ActiveConnection;
daemonPublicKeyB64?: string;
};
const out: string[] = [];
const push = (url: string) => {
if (!out.includes(url)) out.push(url);
};
function sortConnectionsByPreference<T extends { id: string }>(
connections: T[],
preferredId: string | null
): T[] {
if (!preferredId) return connections;
const idx = connections.findIndex((c) => c.id === preferredId);
if (idx === -1) return connections;
return [connections[idx]!, ...connections.slice(0, idx), ...connections.slice(idx + 1)];
}
const isLastKnownRelay = !!relayEndpoint && lastKnownGood === relayEndpoint;
const directEndpoints = endpoints;
function buildCandidates(host: HostProfile): Candidate[] {
const preferred = host.preferredConnectionId ?? null;
if (sessionId && relayEndpoint && isLastKnownRelay) {
push(buildRelayWebSocketUrl({ endpoint: relayEndpoint, sessionId }));
} else if (lastKnownGood) {
push(buildDaemonWebSocketUrl(lastKnownGood));
const direct = sortConnectionsByPreference(
host.connections.filter((c) => c.type === "direct"),
preferred
);
const relay = sortConnectionsByPreference(
host.connections.filter((c) => c.type === "relay"),
preferred
);
const out: Candidate[] = [];
for (const conn of direct) {
out.push({
connectionId: conn.id,
url: buildDaemonWebSocketUrl(conn.endpoint),
activeConnection: { type: "direct", endpoint: conn.endpoint, display: conn.endpoint },
});
}
for (const endpoint of directEndpoints) {
push(buildDaemonWebSocketUrl(endpoint));
}
if (sessionId && relayEndpoint) {
push(buildRelayWebSocketUrl({ endpoint: relayEndpoint, sessionId }));
for (const conn of relay) {
out.push({
connectionId: conn.id,
url: buildRelayWebSocketUrl({
endpoint: conn.relayEndpoint,
serverId: host.serverId,
}),
activeConnection: { type: "relay", endpoint: conn.relayEndpoint, display: "relay" },
daemonPublicKeyB64: conn.daemonPublicKeyB64,
});
}
return out;
@@ -43,55 +68,50 @@ function buildCandidateUrls(daemon: HostProfile): string[] {
function ManagedDaemonSession({ daemon }: { daemon: HostProfile }) {
const { connectionStates } = useDaemonConnections();
const { updateDaemon } = useDaemonRegistry();
const { updateHost } = useDaemonRegistry();
const candidates = useMemo(() => buildCandidateUrls(daemon), [daemon]);
const candidates = useMemo(() => buildCandidates(daemon), [daemon]);
const [activeIndex, setActiveIndex] = useState(0);
const activeUrl = candidates[activeIndex] ?? candidates[0] ?? null;
const active = candidates[activeIndex] ?? candidates[0] ?? null;
const activeUrl = active?.url ?? null;
const lastAttemptedUrlRef = useRef<string | null>(null);
const pendingMetadataWriteRef = useRef(false);
const pendingPreferenceWriteRef = useRef(false);
const upgradeProbeInFlightRef = useRef(false);
useEffect(() => {
if (!activeUrl) {
return;
}
// If the active URL fell out of the candidate set (e.g. endpoints updated), snap back.
const idx = candidates.indexOf(activeUrl);
const idx = candidates.findIndex((c) => c.url === activeUrl);
if (idx === -1) {
setActiveIndex(0);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [candidates.join("|")]);
}, [candidates.map((c) => c.url).join("|")]);
if (!activeUrl) {
return null;
}
const connection = connectionStates.get(daemon.id);
const connection = connectionStates.get(daemon.serverId);
const status = connection?.status ?? "idle";
const lastError = connection?.lastError ?? null;
useEffect(() => {
if (!connection) return;
if (status === "online") {
if (pendingMetadataWriteRef.current) return;
const endpoint = (() => {
try {
return extractHostPortFromWebSocketUrl(activeUrl);
} catch {
return null;
}
})();
if (!endpoint) return;
if (daemon.metadata?.lastKnownGoodEndpoint === endpoint) return;
if (status === "online") {
if (!active) return;
if (pendingPreferenceWriteRef.current) return;
if (daemon.preferredConnectionId === active.connectionId) return;
pendingMetadataWriteRef.current = true;
void updateDaemon(daemon.id, {
metadata: { ...(daemon.metadata ?? {}), lastKnownGoodEndpoint: endpoint },
pendingPreferenceWriteRef.current = true;
void updateHost(daemon.serverId, {
preferredConnectionId: active.connectionId,
}).finally(() => {
pendingMetadataWriteRef.current = false;
pendingPreferenceWriteRef.current = false;
});
return;
}
@@ -106,23 +126,67 @@ function ManagedDaemonSession({ daemon }: { daemon: HostProfile }) {
}
}
}, [
active,
activeIndex,
activeUrl,
candidates.length,
daemon.id,
daemon.metadata,
daemon.preferredConnectionId,
daemon.serverId,
lastError,
status,
updateDaemon,
updateHost,
connection,
]);
useEffect(() => {
if (!connection) return;
if (status !== "online") return;
if (activeIndex === 0) return;
const best = candidates[0];
if (!best) return;
if (best.activeConnection.type !== "direct") return;
const intervalMs = 15_000;
let cancelled = false;
const attemptUpgrade = async () => {
if (cancelled) return;
if (upgradeProbeInFlightRef.current) return;
if (activeIndex === 0) return;
upgradeProbeInFlightRef.current = true;
try {
const { serverId } = await probeDaemonEndpoint(best.activeConnection.endpoint, {
timeoutMs: 2000,
});
if (cancelled) return;
if (serverId !== daemon.serverId) return;
lastAttemptedUrlRef.current = null;
setActiveIndex(0);
} catch {
// ignore - we'll retry periodically
} finally {
upgradeProbeInFlightRef.current = false;
}
};
void attemptUpgrade();
const interval = setInterval(() => void attemptUpgrade(), intervalMs);
return () => {
cancelled = true;
clearInterval(interval);
};
}, [activeIndex, candidates, connection, daemon.serverId, status]);
return (
<SessionProvider
key={`${daemon.id}:${activeUrl}`}
key={`${daemon.serverId}:${activeUrl}`}
serverUrl={activeUrl}
serverId={daemon.id}
daemonPublicKeyB64={daemon.daemonPublicKeyB64}
serverId={daemon.serverId}
activeConnection={active?.activeConnection ?? null}
daemonPublicKeyB64={active?.daemonPublicKeyB64}
>
{null}
</SessionProvider>
@@ -138,7 +202,7 @@ export function MultiDaemonSessionHost() {
return (
<>
{daemons.map((daemon) => (
<ManagedDaemonSession key={daemon.id} daemon={daemon} />
<ManagedDaemonSession key={daemon.serverId} daemon={daemon} />
))}
</>
);