import { useAtomValue } from "@effect/atom-react"; import type { EnvironmentId } from "@t3tools/contracts"; import { automaticServerUpdateDecision } from "@t3tools/client-runtime/state/server"; import * as Option from "effect/Option"; import { useEffect, useRef } from "react"; import { APP_VERSION } from "~/branding"; import { useEnvironments } from "~/state/environments"; import { environmentShell } from "~/state/shell"; import { serverEnvironment } from "~/state/server"; import { useAtomCommand } from "~/state/use-atom-command"; function EnvironmentAutomaticServerUpdate({ connected, environmentId, }: { readonly connected: boolean; readonly environmentId: EnvironmentId; }) { const serverConfig = useAtomValue(serverEnvironment.configValueAtom(environmentId)); const shell = useAtomValue(environmentShell.stateValueAtom(environmentId)); const updateState = useAtomValue(serverEnvironment.updateStateAtom(environmentId)); const updateServer = useAtomCommand(serverEnvironment.updateServer, { reportFailure: false }); const setAutomaticUpdatePending = useAtomCommand(serverEnvironment.setAutomaticUpdatePending, { reportFailure: false, }); const attemptedTargetRef = useRef(null); const decision = automaticServerUpdateDecision({ enabled: connected && (serverConfig?.settings.automaticallyUpdateWhenIdle ?? false), selfUpdate: serverConfig?.environment.capabilities.serverSelfUpdate ?? null, clientVersion: APP_VERSION, serverVersion: serverConfig?.environment.serverVersion ?? "", shellStatus: shell.status, threads: Option.isSome(shell.snapshot) ? shell.snapshot.value.threads : [], }); const decisionStatus = decision.status; const decisionReason = decision.status === "pending" ? decision.reason : null; const fromVersion = decision.status === "ineligible" ? null : decision.fromVersion; const targetVersion = decision.status === "ineligible" ? null : decision.targetVersion; useEffect(() => { if (decisionStatus !== "ready" || fromVersion === null || targetVersion === null) { attemptedTargetRef.current = null; } if (decisionStatus === "pending" && fromVersion !== null && targetVersion !== null) { const pendingMatches = updateState.status === "pending" && updateState.reason === decisionReason && updateState.fromVersion === fromVersion && updateState.targetVersion === targetVersion; if (!pendingMatches) { void setAutomaticUpdatePending({ environmentId, pending: { status: "pending", reason: decisionReason ?? "synchronizing", fromVersion, targetVersion, }, }); } return; } if (decisionStatus === "ineligible") { if (updateState.status === "pending") { void setAutomaticUpdatePending({ environmentId, pending: null }); } return; } if (decisionStatus !== "ready" || fromVersion === null || targetVersion === null) { return; } const targetKey = `${fromVersion}->${targetVersion}`; if (updateState.status === "running") { attemptedTargetRef.current = targetKey; return; } if ( updateState.status === "failed" && updateState.fromVersion === fromVersion && updateState.targetVersion === targetVersion ) { attemptedTargetRef.current = targetKey; return; } if (attemptedTargetRef.current === targetKey) { return; } attemptedTargetRef.current = targetKey; void updateServer({ environmentId, input: { targetVersion, automatic: true }, }); }, [ decisionReason, decisionStatus, environmentId, fromVersion, setAutomaticUpdatePending, targetVersion, updateServer, updateState, ]); return null; } export function AutomaticServerUpdateCoordinator() { const { environments } = useEnvironments(); return environments.map((environment) => ( )); }