import { useAuth } from "@clerk/react"; import { findErrorTraceId } from "@t3tools/client-runtime/errors"; import { isAtomCommandInterrupted, settlePromise, squashAtomCommandFailure, } from "@t3tools/client-runtime/state/runtime"; import { useState } from "react"; import { toastManager } from "../components/ui/toast"; import { relayEnvironmentDiscovery } from "../state/relay"; import { useAtomCommand } from "../state/use-atom-command"; import { linkPrimaryEnvironment as linkPrimaryEnvironmentAtom, unlinkPrimaryEnvironment as unlinkPrimaryEnvironmentAtom, updatePrimaryEnvironmentPreferences as updatePrimaryEnvironmentPreferencesAtom, } from "./linkEnvironmentAtoms"; import { usePrimaryCloudLinkState } from "./primaryCloudLinkState"; import { resolveRelayClerkTokenOptions } from "./publicConfig"; export interface CloudLinkDesiredState { readonly managedTunnel: boolean; readonly publish: boolean; } /** * Drives the primary environment's T3 Connect link. T3 Connect (managed * tunnel) and agent-activity publishing are independent capabilities backed by * a single relay link, so consumers express the full desired state and * `reconcileCloudState` applies it: unlink when neither is wanted, otherwise * (re)link with the mode the managed-tunnel bit implies and set the publish * preference. Re-linking only happens when the managed-tunnel mode actually * changes, so flipping publish alone is cheap. */ export function useCloudLinkController() { const { getToken, isSignedIn } = useAuth(); const refreshRelayEnvironments = useAtomCommand(relayEnvironmentDiscovery.refresh, { reportFailure: false, }); const linkPrimaryEnvironment = useAtomCommand(linkPrimaryEnvironmentAtom, { reportFailure: false, }); const unlinkPrimaryEnvironment = useAtomCommand(unlinkPrimaryEnvironmentAtom, { reportFailure: false, }); const updatePrimaryEnvironmentPreferences = useAtomCommand( updatePrimaryEnvironmentPreferencesAtom, { reportFailure: false }, ); const primaryCloudLinkState = usePrimaryCloudLinkState(); const [operationError, setOperationError] = useState(null); const reportUpdateFailure = (cause: unknown) => { const message = cause instanceof Error ? cause.message : "Could not update T3 Connect access."; const traceId = findErrorTraceId(cause); console.error("[t3-connect] Could not update T3 Connect", { message, traceId, cause }); setOperationError(traceId ? `${message} Trace ID: ${traceId}` : message); toastManager.add({ type: "error", title: "Could not update T3 Connect", description: message, data: traceId ? { secondaryActionProps: { children: "Copy trace ID", onClick: () => void navigator.clipboard?.writeText(traceId), }, } : undefined, }); }; // Older environment servers predate the managedTunnelActive field; for them a // link always implies a managed tunnel, so fall back to `linked`. const managedTunnelActive = primaryCloudLinkState.data?.managedTunnelActive ?? primaryCloudLinkState.data?.linked ?? false; const publishAgentActivity = primaryCloudLinkState.data?.publishAgentActivity ?? false; const linked = primaryCloudLinkState.data?.linked ?? false; const reconcileCloudState = async (desired: CloudLinkDesiredState): Promise => { setOperationError(null); const target = primaryCloudLinkState.target; if (!target) { reportUpdateFailure(new Error("Local environment is not ready yet.")); return false; } const tokenResult = await settlePromise(() => getToken(resolveRelayClerkTokenOptions())); const wantsLink = desired.managedTunnel || desired.publish; // A failure after this point may follow a partially applied mutation (e.g. // the link succeeded but the preference update did not), so every exit — // success or failure — refreshes the rendered state to whatever the server // actually holds now. if (!wantsLink) { // Unlink works without a relay token — a failed token read must not // leave the user unable to turn T3 Connect off. const unlinkResult = await unlinkPrimaryEnvironment({ target, clerkToken: tokenResult._tag === "Success" ? (tokenResult.value ?? null) : null, }); if (unlinkResult._tag === "Failure") { if (!isAtomCommandInterrupted(unlinkResult)) { reportUpdateFailure(squashAtomCommandFailure(unlinkResult)); } primaryCloudLinkState.refresh(); return false; } } else { if (tokenResult._tag === "Failure") { reportUpdateFailure(squashAtomCommandFailure(tokenResult)); return false; } const clerkToken = tokenResult.value; if (!clerkToken) { reportUpdateFailure(new Error("Sign in to T3 Connect before enabling this.")); return false; } if (!linked || managedTunnelActive !== desired.managedTunnel) { const linkResult = await linkPrimaryEnvironment({ target, clerkToken, mode: desired.managedTunnel ? "managed" : "publish_only", }); if (linkResult._tag === "Failure") { if (!isAtomCommandInterrupted(linkResult)) { reportUpdateFailure(squashAtomCommandFailure(linkResult)); } primaryCloudLinkState.refresh(); return false; } } const prefResult = await updatePrimaryEnvironmentPreferences({ target, publishAgentActivity: desired.publish, }); if (prefResult._tag === "Failure") { if (!isAtomCommandInterrupted(prefResult)) { reportUpdateFailure(squashAtomCommandFailure(prefResult)); } primaryCloudLinkState.refresh(); return false; } } primaryCloudLinkState.refresh(); const refreshResult = await refreshRelayEnvironments(); if (refreshResult._tag === "Failure" && !isAtomCommandInterrupted(refreshResult)) { reportUpdateFailure(squashAtomCommandFailure(refreshResult)); return false; } return true; }; return { isSignedIn, linkState: primaryCloudLinkState, linked, managedTunnelActive, publishAgentActivity, operationError, reconcileCloudState, }; }