1import { useAtomValue } from "@effect/atom-react";
2import {
3 AuthRelayReadScope,
4 EnvironmentId,
5 type EnvironmentCloudLinkStateResult,
6} from "@t3tools/contracts";
7import * as Cause from "effect/Cause";
8import * as Context from "effect/Context";
9import * as Effect from "effect/Effect";
10import * as Layer from "effect/Layer";
11import * as Option from "effect/Option";
12import { AsyncResult, Atom } from "effect/reactivity";
13import { HttpClient } from "effect/http";
14import { useCallback, useMemo } from "react";
15
16import { usePrimaryEnvironment } from "../state/environments";
17import { runtime } from "../lib/runtime";
18import { appAtomRegistry } from "../rpc/atomRegistry";
19import { readEnvironmentScope, useEnvironmentScope } from "../state/session";
20import { readPrimaryCloudLinkState, type CloudLinkTarget } from "./linkEnvironment";
21import { hasCloudPublicConfig } from "./publicConfig";
22
23const primaryCloudLinkAtomRuntime = Atom.runtime(
24 Layer.effect(
25 HttpClient.HttpClient,
26 runtime.contextEffect.pipe(
27 Effect.map((context) => Context.get(context, HttpClient.HttpClient)),
28 ),
29 ),
30);
31
32const primaryCloudLinkStateAtom = Atom.family((key: string) => {
33 const target = JSON.parse(key) as CloudLinkTarget;
34 return primaryCloudLinkAtomRuntime
35 .atom(readPrimaryCloudLinkState({ target }))
36 .pipe(
37 Atom.swr({ staleTime: 5_000, revalidateOnMount: true }),
38 Atom.setIdleTTL(5 * 60_000),
39 Atom.withLabel(`primary-cloud-link:${target.environmentId}`),
40 );
41});
42
43const EMPTY_PRIMARY_CLOUD_LINK_STATE_ATOM = Atom.make(
44 AsyncResult.success<EnvironmentCloudLinkStateResult | null>(null),
45).pipe(Atom.keepAlive, Atom.withLabel("primary-cloud-link:null"));
46
47function targetKey(target: CloudLinkTarget): string {
48 return JSON.stringify(target);
49}
50
51function refreshPrimaryCloudLinkState(target: CloudLinkTarget | null): void {
52 if (
53 target &&
54 readEnvironmentScope(EnvironmentId.make(target.environmentId), AuthRelayReadScope)
55 ) {
56 appAtomRegistry.refresh(primaryCloudLinkStateAtom(targetKey(target)));
57 }
58}
59
60export function readCachedPrimaryCloudLinkState(target: CloudLinkTarget) {
61 if (!readEnvironmentScope(EnvironmentId.make(target.environmentId), AuthRelayReadScope))
62 return null;
63 const result = appAtomRegistry.get(primaryCloudLinkStateAtom(targetKey(target)));
64 return result._tag === "Success" ? result.value : null;
65}
66
67export function usePrimaryCloudLinkState() {
68 const primary = usePrimaryEnvironment();
69 const canReadRelay = useEnvironmentScope(primary?.environmentId ?? null, AuthRelayReadScope);
70 const target = useMemo(
71 () =>
72 primary?.entry.target._tag === "PrimaryConnectionTarget"
73 ? {
74 environmentId: primary.environmentId,
75 label: primary.label,
76 httpBaseUrl: primary.entry.target.httpBaseUrl,
77 wsBaseUrl: primary.entry.target.wsBaseUrl,
78 }
79 : null,
80 [primary],
81 );
82 // Builds without T3 Connect have no link to read; skip the request.
83 const atom =
84 target && hasCloudPublicConfig() && canReadRelay
85 ? primaryCloudLinkStateAtom(targetKey(target))
86 : EMPTY_PRIMARY_CLOUD_LINK_STATE_ATOM;
87 const result = useAtomValue(atom);
88 const refresh = useCallback(() => {
89 refreshPrimaryCloudLinkState(target);
90 }, [target]);
91 let error: string | null = null;
92 if (result._tag === "Failure") {
93 const cause = Cause.squash(result.cause);
94 error = cause instanceof Error ? cause.message : "Could not read T3 Connect link state.";
95 }
96
97 return {
98 data: result._tag === "Failure" ? null : Option.getOrNull(AsyncResult.value(result)),
99 error,
100 isPending: result.waiting,
101 refresh,
102 target,
103 };
104}