1import { type ConnectionCatalogEntry, hasRelayRoute } from "@t3tools/client-runtime/connection";
2import type { EnvironmentId } from "@t3tools/contracts";
3
4interface OnboardingEnvironment {
5 readonly environmentId: EnvironmentId;
6 readonly connection: { readonly phase: string };
7 readonly entry: Pick<ConnectionCatalogEntry, "target" | "alternateRoutes">;
8}
9
10export function isOnboardingRelayEnvironment(
11 environment: Pick<OnboardingEnvironment, "entry">,
12): boolean {
13 return hasRelayRoute(environment.entry);
14}
15
16/** Keep a directly paired machine pinned while its initial connection completes. */
17export function resolveOnboardingTargetEnvironment<TEnvironment extends OnboardingEnvironment>({
18 mode,
19 environments,
20 primaryEnvironment,
21 pairedEnvironmentId,
22}: {
23 readonly mode: "local" | "connect" | "direct";
24 readonly environments: ReadonlyArray<TEnvironment>;
25 readonly primaryEnvironment: TEnvironment | null;
26 readonly pairedEnvironmentId: EnvironmentId | null;
27}): TEnvironment | null {
28 if (mode === "direct" && pairedEnvironmentId !== null) {
29 const pairedEnvironment = environments.find(
30 (environment) => environment.environmentId === pairedEnvironmentId,
31 );
32 return pairedEnvironment?.connection.phase === "connected" ? pairedEnvironment : null;
33 }
34
35 const connectedRelayEnvironments = environments.filter(
36 (environment) =>
37 environment.connection.phase === "connected" && isOnboardingRelayEnvironment(environment),
38 );
39
40 if (mode === "connect" && connectedRelayEnvironments.length > 0) {
41 return connectedRelayEnvironments[connectedRelayEnvironments.length - 1] ?? null;
42 }
43
44 if (primaryEnvironment?.connection.phase === "connected") {
45 return primaryEnvironment;
46 }
47
48 return mode === "local" ? null : (connectedRelayEnvironments[0] ?? null);
49}
50
51/**
52 * The computers the wizard sets up from the user's selection. Continue waits
53 * only on a first connection attempt, which settles on its own. Selected
54 * computers that are switched off, offline, failing, or unsupported are skipped
55 * so they can never lock the user out of onboarding.
56 */
57export function resolveOnboardingSetup(
58 environments: ReadonlyArray<Pick<OnboardingEnvironment, "environmentId" | "connection">>,
59 selectedIds: ReadonlySet<EnvironmentId>,
60): {
61 readonly ready: boolean;
62 readonly environmentIds: ReadonlyArray<EnvironmentId>;
63 readonly skippedIds: ReadonlyArray<EnvironmentId>;
64} {
65 const selected = environments.filter((environment) => selectedIds.has(environment.environmentId));
66 const idsInPhase = (keep: (phase: string) => boolean) =>
67 selected
68 .filter((environment) => keep(environment.connection.phase))
69 .map((environment) => environment.environmentId);
70 const environmentIds = idsInPhase((phase) => phase === "connected");
71 const settling = selected.some((environment) => environment.connection.phase === "connecting");
72 return {
73 ready: environmentIds.length > 0 && !settling,
74 environmentIds,
75 skippedIds: idsInPhase((phase) => phase !== "connected" && phase !== "connecting"),
76 };
77}