Skip to content
Source
Sign inSign up

session.ts

106 lines3.6 KB

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";
11​
12import { connectionAtomRuntime } from "../connection/runtime";
13import { appAtomRegistry } from "../rpc/atomRegistry";
14import { environmentAtomList, environmentIdsKey } from "./environmentAtomList";
15​
16export const environmentSession = createEnvironmentSessionAtoms(connectionAtomRuntime);
17export const environmentSessionStatesAtom = environmentAtomList(
18 "web-environment-session-states",
19 environmentSession.sessionStateAtom,
20);
21​
22const EMPTY_SESSION_STATE_ATOM = Atom.make(AsyncResult.initial<AuthSessionState>());
23​
24export function useEnvironmentScope(
25 environmentId: EnvironmentId | null,
26 scope: AuthEnvironmentScope,
27): boolean {
28 // Select the grant: every revalidation emits a new result, and most consumers are list rows.
29 return useAtomValue(
30 environmentId === null
31 ? EMPTY_SESSION_STATE_ATOM
32 : environmentSession.sessionStateAtom(environmentId),
33 (result) => sessionHasScope(result, scope),
34 );
36​
37export function sessionHasScope(
38 result: AsyncResult.AsyncResult<AuthSessionState, unknown>,
39 scope: AuthEnvironmentScope,
40): boolean {
41 const session = AsyncResult.getOrElse(result, () => null);
42 return result._tag !== "Failure" && session !== null && sessionGrantsScope(session, scope);
44​
45const environmentsWithScopeAtom = Atom.family((key: string) => {
46 const [scope, idsKey = ""] = key.split(/\n(.*)/s) as [AuthEnvironmentScope, string?];
47 const ids = idsKey === "" ? [] : (idsKey.split("\n") as EnvironmentId[]);
48 const statesAtom = environmentSessionStatesAtom(idsKey);
49 return Atom.make((get) => {
50 const states = get(statesAtom);
51 return new Set(ids.filter((_, index) => sessionHasScope(states[index]!, scope)));
52 }).pipe(Atom.withLabel(`web-environments-with-scope:${key}`));
53});
54​
55export function useEnvironmentsWithScope(
56 environments: ReadonlyArray<{ readonly environmentId: EnvironmentId }>,
57 scope: AuthEnvironmentScope,
58): ReadonlySet<EnvironmentId> {
59 return useAtomValue(
60 environmentsWithScopeAtom(
61 `${scope}\n${environmentIdsKey(environments.map(({ environmentId }) => environmentId))}`,
62 ),
63 );
65​
66export function readEnvironmentScope(
67 environmentId: EnvironmentId,
68 scope: AuthEnvironmentScope,
69): boolean {
70 return sessionHasScope(
71 appAtomRegistry.get(environmentSession.sessionStateAtom(environmentId)),
72 scope,
73 );
75​
76const EMPTY_PREPARED_CONNECTION_ATOM = Atom.make(Option.none()).pipe(
77 Atom.withLabel("web-prepared-connection:empty"),
78);
79​
80export function usePreparedConnection(environmentId: EnvironmentId | null) {
81 return useAtomValue(
82 environmentId === null
83 ? EMPTY_PREPARED_CONNECTION_ATOM
84 : environmentSession.preparedConnectionValueAtom(environmentId),
85 );
87​
88export function readPreparedConnection(environmentId: EnvironmentId) {
89 return Option.getOrNull(
90 appAtomRegistry.get(environmentSession.preparedConnectionValueAtom(environmentId)),
91 );
93​
94/** data survives SWR revalidation; isPending applies only before first resolve. */
95export function useEnvironmentSessionState(environmentId: EnvironmentId | null) {
96 const result = useAtomValue(
97 environmentId === null
98 ? EMPTY_SESSION_STATE_ATOM
99 : environmentSession.sessionStateAtom(environmentId),
100 );
101 return {
102 data: AsyncResult.getOrElse(result, () => null),
103 hasError: result._tag === "Failure",
104 isPending: result.waiting,
105 };