1import { useAtomValue } from "@effect/atom-react";
2import { createEnvironmentSessionAtoms } from "@t3tools/client-runtime/state/session";
3import {
4 type AuthEnvironmentScope,
5 type AuthSessionState,
6 type EnvironmentId,
7 sessionGrantsScope,
8} from "@t3tools/contracts";
9import * as Option from "effect/Option";
10import { AsyncResult, Atom } from "effect/reactivity";
11import { useMemo } from "react";
12
13import { connectionAtomRuntime } from "../connection/runtime";
14import { appAtomRegistry } from "./atom-registry";
15
16export const environmentSession = createEnvironmentSessionAtoms(connectionAtomRuntime);
17
18const EMPTY_SESSION_STATE_ATOM = Atom.make(AsyncResult.initial<AuthSessionState>());
19
20export function sessionHasScope(
21 result: AsyncResult.AsyncResult<AuthSessionState, unknown>,
22 scope: AuthEnvironmentScope,
23): boolean {
24 const session = AsyncResult.getOrElse(result, () => null);
25 return result._tag !== "Failure" && session !== null && sessionGrantsScope(session, scope);
26}
27
28export function useEnvironmentScope(
29 environmentId: EnvironmentId | null,
30 scope: AuthEnvironmentScope,
31): boolean {
32 // Select the grant: every revalidation emits a new result, and most consumers are list rows.
33 return useAtomValue(
34 environmentId === null
35 ? EMPTY_SESSION_STATE_ATOM
36 : environmentSession.sessionStateAtom(environmentId),
37 (result) => sessionHasScope(result, scope),
38 );
39}
40
41export function useEnvironmentsWithScope(
42 environments: ReadonlyArray<{ readonly environmentId: EnvironmentId }>,
43 scope: AuthEnvironmentScope,
44): ReadonlySet<EnvironmentId> {
45 const permittedEnvironments = useMemo(
46 () =>
47 Atom.make((get) => {
48 const permitted = new Set<EnvironmentId>();
49 for (const { environmentId } of environments) {
50 if (sessionHasScope(get(environmentSession.sessionStateAtom(environmentId)), scope)) {
51 permitted.add(environmentId);
52 }
53 }
54 return permitted;
55 }),
56 [environments, scope],
57 );
58 return useAtomValue(permittedEnvironments);
59}
60
61export function readEnvironmentScope(
62 environmentId: EnvironmentId,
63 scope: AuthEnvironmentScope,
64): boolean {
65 const result = appAtomRegistry.get(environmentSession.sessionStateAtom(environmentId));
66 return sessionHasScope(result, scope);
67}
68
69const EMPTY_PREPARED_CONNECTION_ATOM = Atom.make(Option.none()).pipe(
70 Atom.withLabel("mobile-prepared-connection:empty"),
71);
72
73export function usePreparedConnection(environmentId: EnvironmentId | null) {
74 return useAtomValue(
75 environmentId === null
76 ? EMPTY_PREPARED_CONNECTION_ATOM
77 : environmentSession.preparedConnectionValueAtom(environmentId),
78 );
79}