1import { useAuth } from "@clerk/react";
2import { ManagedRelay, setManagedRelaySession } from "@t3tools/client-runtime/relay";
3import {
4 reportAtomCommandResult,
5 settleAsyncResult,
6 settlePromise,
7} from "@t3tools/client-runtime/state/runtime";
8import * as Effect from "effect/Effect";
9import { useEffect, useRef, type ReactNode } from "react";
10
11import { environmentCatalog } from "../connection/catalog";
12import { runtime } from "../lib/runtime";
13import { appAtomRegistry } from "../rpc/atomRegistry";
14import { useAtomCommand } from "../state/use-atom-command";
15import { deactivateCloudAuthentication } from "./activation";
16import { resolveRelayClerkTokenOptions } from "./publicConfig";
17
18export function deactivateManagedRelayAuthentication(): void {
19 setManagedRelaySession(appAtomRegistry, null);
20}
21
22export function activateManagedRelayAuthentication(
23 accountId: string,
24 readClerkToken: () => Promise<string | null>,
25): void {
26 setManagedRelaySession(appAtomRegistry, {
27 accountId,
28 readClerkToken,
29 });
30}
31
32export function ManagedRelayAuthProvider({ children }: { readonly children: ReactNode }) {
33 const { getToken, isLoaded, isSignedIn, userId } = useAuth({
34 treatPendingAsSignedOut: false,
35 });
36 const removeRelayEnvironments = useAtomCommand(environmentCatalog.removeRelayEnvironments, {
37 reportFailure: false,
38 reportDefect: false,
39 });
40 const observedAccountRef = useRef<string | null | undefined>(undefined);
41 const accountTransitionRef = useRef<Promise<void> | null>(null);
42
43 useEffect(() => {
44 if (!isLoaded) {
45 return;
46 }
47
48 let cancelled = false;
49 const previousAccount = observedAccountRef.current;
50 const nextAccount = isSignedIn && userId ? userId : null;
51 observedAccountRef.current = nextAccount;
52
53 const queueAccountCleanup = () => {
54 const previousTransition = accountTransitionRef.current ?? Promise.resolve();
55 accountTransitionRef.current = previousTransition.then(async () => {
56 const results = await Promise.all([
57 removeRelayEnvironments(),
58 settleAsyncResult(() =>
59 runtime.runPromiseExit(
60 ManagedRelay.ManagedRelayClient.pipe(
61 Effect.flatMap((client) => client.resetTokenCache),
62 ),
63 ),
64 ),
65 ]);
66 for (const result of results) {
67 reportAtomCommandResult(result, { label: "cloud account cleanup" });
68 }
69 });
70 return accountTransitionRef.current;
71 };
72
73 if (!isSignedIn || !userId) {
74 deactivateManagedRelayAuthentication();
75 if (previousAccount !== undefined && previousAccount !== null)
76 deactivateCloudAuthentication();
77 if (previousAccount !== null) {
78 void queueAccountCleanup();
79 }
80 } else {
81 const tokenProvider = () => getToken(resolveRelayClerkTokenOptions());
82 const activateSession = () => {
83 if (!cancelled) {
84 activateManagedRelayAuthentication(userId, tokenProvider);
85 }
86 };
87 const activateAfterTransition = (transition: Promise<void>) => {
88 void (async () => {
89 const result = await settlePromise(async () => {
90 await transition;
91 activateSession();
92 });
93 reportAtomCommandResult(result, { label: "cloud account activation" });
94 })();
95 };
96 if (previousAccount !== undefined && previousAccount !== null && previousAccount !== userId) {
97 deactivateManagedRelayAuthentication();
98 activateAfterTransition(queueAccountCleanup());
99 } else {
100 activateAfterTransition(accountTransitionRef.current ?? Promise.resolve());
101 }
102 }
103 return () => {
104 cancelled = true;
105 };
106 }, [getToken, isLoaded, isSignedIn, removeRelayEnvironments, userId]);
107
108 useEffect(() => () => deactivateManagedRelayAuthentication(), []);
109
110 return children;
111}