1import { hubConnection } from "../connection/hub";
2import { useAtomValue } from "@effect/atom-react";
3import { createEnvironmentSessionAtoms } from "@t3tools/client-runtime/state/session";
4import {
5 AuthOrchestrationOperateScope,
6 type AuthEnvironmentScope,
7 type AuthSessionState,
8 type EnvironmentId,
9 sessionGrantsScope,
10} from "@t3tools/contracts";
11import { useMemo } from "react";
12import * as Option from "effect/Option";
13import { AsyncResult, Atom } from "effect/reactivity";
14
15import { connectionAtomRuntime } from "../connection/runtime";
16import { appAtomRegistry } from "../rpc/atomRegistry";
17
18export const environmentSession = createEnvironmentSessionAtoms(connectionAtomRuntime);
19
20const EMPTY_SESSION_STATE_ATOM = Atom.make(AsyncResult.initial<AuthSessionState>());
21
22/** Uses the selected environment's grant, including cached scopes during a refresh. */
23export function useEnvironmentScope(
24 environmentId: EnvironmentId | null,
25 scope: AuthEnvironmentScope,
26): boolean {
27 const result = useAtomValue(
28 environmentId === null
29 ? EMPTY_SESSION_STATE_ATOM
30 : environmentSession.sessionStateAtom(environmentId),
31 );
32 return sessionHasScope(result, scope);
33}
34
35function sessionHasScope(
36 result: AsyncResult.AsyncResult<AuthSessionState, unknown>,
37 scope: AuthEnvironmentScope,
38): boolean {
39 const session = Option.getOrNull(AsyncResult.value(result));
40 return result._tag !== "Failure" && session !== null && sessionGrantsScope(session, scope);
41}
42
43/** Subscribe to the grants of every selected environment. */
44export function useEnvironmentsWithScope(
45 environments: ReadonlyArray<{ readonly environmentId: EnvironmentId }>,
46 scope: AuthEnvironmentScope,
47): ReadonlySet<EnvironmentId> {
48 const permitted = useMemo(
49 () =>
50 Atom.make((get) => {
51 const ids = new Set<EnvironmentId>();
52 for (const { environmentId } of environments) {
53 const result = get(environmentSession.sessionStateAtom(environmentId));
54 if (sessionHasScope(result, scope)) {
55 ids.add(environmentId);
56 }
57 }
58 return ids;
59 }),
60 [environments, scope],
61 );
62 return useAtomValue(permitted);
63}
64
65export function readEnvironmentScope(
66 environmentId: EnvironmentId,
67 scope: AuthEnvironmentScope,
68): boolean {
69 return sessionHasScope(
70 appAtomRegistry.get(environmentSession.sessionStateAtom(environmentId)),
71 scope,
72 );
73}
74
75const EMPTY_PREPARED_CONNECTION_ATOM = Atom.make(Option.none()).pipe(
76 Atom.withLabel("web-prepared-connection:empty"),
77);
78
79export function usePreparedConnection(environmentId: EnvironmentId | null) {
80 return useAtomValue(
81 environmentId === null
82 ? EMPTY_PREPARED_CONNECTION_ATOM
83 : environmentSession.preparedConnectionValueAtom(environmentId),
84 );
85}
86
87export function readPreparedConnection(environmentId: EnvironmentId) {
88 return Option.getOrNull(
89 appAtomRegistry.get(environmentSession.preparedConnectionValueAtom(environmentId)),
90 );
91}
92
93/**
94 * This client's authenticated session on one environment, as reported by that
95 * environment's `/api/auth/session` endpoint. `data` stays populated across
96 * SWR revalidations; `isPending` is only meaningful before the first resolve.
97 */
98export function useEnvironmentSessionState(environmentId: EnvironmentId) {
99 const result = useAtomValue(environmentSession.sessionStateAtom(environmentId));
100 return {
101 data: Option.getOrNull(AsyncResult.value(result)),
102 hasError: result._tag === "Failure",
103 isPending: result.waiting,
104 };
105}
106
107export function useThreadOrganizationPermission(environmentId: EnvironmentId | null): boolean {
108 const source = useEnvironmentScope(environmentId, AuthOrchestrationOperateScope);
109 const central = useAtomValue(hubConnection.organizationPermissionAtom(environmentId));
110 return source || central;
111}
112
113export function readThreadOrganizationPermission(environmentId: EnvironmentId): boolean {
114 return (
115 readEnvironmentScope(environmentId, AuthOrchestrationOperateScope) ||
116 appAtomRegistry.get(hubConnection.organizationPermissionAtom(environmentId))
117 );
118}