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
@@ -1,31 +1,35 @@
import { createContext, useCallback, useContext, useEffect, useState } from "react";
import type { ReactNode } from "react";
import { useDaemonRegistry, type DaemonProfile } from "./daemon-registry-context";
import { useDaemonRegistry, type HostProfile } from "./daemon-registry-context";
export type ActiveConnection =
| { type: "direct"; endpoint: string; display: string }
| { type: "relay"; endpoint: string; display: "relay" };
export type ConnectionState =
| { status: "idle"; lastError: null; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: false }
| { status: "connecting"; lastError: null; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: boolean }
| { status: "online"; lastError: null; lastOnlineAt: string; agentListReady: boolean; hasEverReceivedAgentList: boolean }
| { status: "offline"; lastError: string | null; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: boolean }
| { status: "error"; lastError: string; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: boolean };
| { status: "idle"; activeConnection: ActiveConnection | null; lastError: null; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: false }
| { status: "connecting"; activeConnection: ActiveConnection | null; lastError: null; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: boolean }
| { status: "online"; activeConnection: ActiveConnection | null; lastError: null; lastOnlineAt: string; agentListReady: boolean; hasEverReceivedAgentList: boolean }
| { status: "offline"; activeConnection: ActiveConnection | null; lastError: string | null; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: boolean }
| { status: "error"; activeConnection: ActiveConnection | null; lastError: string; lastOnlineAt: string | null; agentListReady: false; hasEverReceivedAgentList: boolean };
export type ConnectionStatus = ConnectionState["status"];
type ConnectionStateUpdate =
| { status: "idle" }
| { status: "connecting"; lastOnlineAt?: string | null }
| { status: "online"; lastOnlineAt: string; agentListReady?: boolean }
| { status: "offline"; lastError?: string | null; lastOnlineAt?: string | null }
| { status: "error"; lastError: string; lastOnlineAt?: string | null };
| { status: "connecting"; activeConnection?: ActiveConnection | null; lastOnlineAt?: string | null }
| { status: "online"; activeConnection?: ActiveConnection | null; lastOnlineAt: string; agentListReady?: boolean }
| { status: "offline"; activeConnection?: ActiveConnection | null; lastError?: string | null; lastOnlineAt?: string | null }
| { status: "error"; activeConnection?: ActiveConnection | null; lastError: string; lastOnlineAt?: string | null };
export type DaemonConnectionRecord = {
daemon: DaemonProfile;
daemon: HostProfile;
} & ConnectionState;
interface DaemonConnectionsContextValue {
connectionStates: Map<string, DaemonConnectionRecord>;
isLoading: boolean;
updateConnectionStatus: (daemonId: string, update: ConnectionStateUpdate) => void;
updateConnectionStatus: (serverId: string, update: ConnectionStateUpdate) => void;
}
const DaemonConnectionsContext = createContext<DaemonConnectionsContextValue | null>(null);
@@ -33,6 +37,7 @@ const DaemonConnectionsContext = createContext<DaemonConnectionsContextValue | n
function createDefaultConnectionState(): ConnectionState {
return {
status: "idle",
activeConnection: null,
lastError: null,
lastOnlineAt: null,
agentListReady: false,
@@ -48,6 +53,7 @@ function resolveNextConnectionState(
case "idle":
return {
status: "idle",
activeConnection: existing.activeConnection ?? null,
lastError: null,
lastOnlineAt: existing.lastOnlineAt,
agentListReady: false,
@@ -56,6 +62,7 @@ function resolveNextConnectionState(
case "connecting":
return {
status: "connecting",
activeConnection: update.activeConnection ?? existing.activeConnection ?? null,
lastError: null,
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
agentListReady: false,
@@ -66,6 +73,7 @@ function resolveNextConnectionState(
return {
status: "online",
activeConnection: update.activeConnection ?? existing.activeConnection ?? null,
lastError: null,
lastOnlineAt: update.lastOnlineAt,
agentListReady: currentAgentListReady,
@@ -75,6 +83,7 @@ function resolveNextConnectionState(
case "offline":
return {
status: "offline",
activeConnection: update.activeConnection ?? existing.activeConnection ?? null,
lastError: update.lastError ?? null,
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
agentListReady: false,
@@ -83,6 +92,7 @@ function resolveNextConnectionState(
case "error":
return {
status: "error",
activeConnection: update.activeConnection ?? existing.activeConnection ?? null,
lastError: update.lastError,
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
agentListReady: false,
@@ -91,13 +101,13 @@ function resolveNextConnectionState(
}
}
function logConnectionLifecycle(daemon: DaemonProfile, previous: ConnectionState, next: ConnectionState) {
function logConnectionLifecycle(daemon: HostProfile, previous: ConnectionState, next: ConnectionState) {
const severity: "info" | "warn" = next.status === "error" ? "warn" : "info";
const logger = severity === "warn" ? console.warn : console.info;
const logPayload = {
event: "daemon_connection_state",
daemonId: daemon.id,
serverId: daemon.serverId,
label: daemon.label,
from: previous.status,
to: next.status,
@@ -127,8 +137,8 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
setConnectionStates((prev) => {
const next = new Map<string, DaemonConnectionRecord>();
for (const daemon of daemons) {
const existing = prev.get(daemon.id);
next.set(daemon.id, {
const existing = prev.get(daemon.serverId);
next.set(daemon.serverId, {
daemon,
...(existing ?? createDefaultConnectionState()),
});
@@ -139,9 +149,9 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
const updateConnectionStatus = useCallback(
(daemonId: string, update: ConnectionStateUpdate) => {
(serverId: string, update: ConnectionStateUpdate) => {
setConnectionStates((prev) => {
const existing = prev.get(daemonId);
const existing = prev.get(serverId);
if (!existing) {
return prev;
}
@@ -156,7 +166,7 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
}
const next = new Map(prev);
next.set(daemonId, { daemon: existing.daemon, ...nextState });
next.set(serverId, { daemon: existing.daemon, ...nextState });
return next;
});
},