···
1
-
import type { Did } from "@/lib/types/atproto";
1
+
import { DEFAULT_STALE_TIME } from "@/lib/consts";
2
+
import type { AtUri, Did } from "@/lib/types/atproto";
import type { LatticeSessionInfo } from "@/lib/types/handshake";
4
+
import { systemsGmstnDevelopmentChannelRecordSchema } from "@/lib/types/lexicon/systems.gmstn.development.channels";
5
+
import { getRecordFromFullAtUri, stringToAtUri } from "@/lib/utils/atproto";
6
+
import { useOAuthValue } from "@/providers/OAuthProvider";
7
+
import { getMembershipRecordsFromPds } from "@/queries/get-membership-from-pds";
8
+
import { initiateHandshakeTo } from "@/queries/initiate-handshake-to";
9
+
import type { OAuthSession } from "@atproto/oauth-client";
10
+
import { useQueries, useQuery } from "@tanstack/react-query";
import type { ReactNode } from "react";
4
-
import { createContext } from "react";
12
+
import { createContext, useMemo } from "react";
6
-
type LatticeSessions = Map<Did, LatticeSessionInfo>;
14
+
type LatticeSessionsMap = Map<Did, LatticeSessionInfo>;
interface LatticeSessionContextValue {
9
-
sessions: LatticeSessions;
17
+
sessions: LatticeSessionsMap;
18
+
isInitialising: boolean;
19
+
error: Error | null;
20
+
getSession: (latticeDid: Did) => LatticeSessionInfo | undefined;
const LatticeSessionsContext = createContext<LatticeSessionContextValue | null>(
···
21
-
return <LatticeSessionsContext>{children}</LatticeSessionsContext>;
32
+
const oauth = useOAuthValue();
34
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Explicit guard
37
+
"LatticeSessionsProvider must be used within an OAuth provider.",
40
+
const { session, isLoading, agent, client } = oauth;
41
+
const isOAuthReady = !isLoading && !!agent && !!client && !!session;
43
+
const membershipsQuery = useQuery({
44
+
queryKey: ["membership", session?.did],
45
+
enabled: isOAuthReady,
46
+
queryFn: async () => {
47
+
if (!session) throw new Error("We need an OAuth session");
48
+
return await membershipQueryFn(session);
50
+
staleTime: DEFAULT_STALE_TIME,
53
+
// TODO: group channel memberships by
55
+
const channelsQueries = useQueries({
56
+
queries: !membershipsQuery.data
58
+
: membershipsQuery.data.map((membershipQueryResult) => ({
59
+
enabled: membershipsQuery.isSuccess,
62
+
membershipQueryResult.membership.channel.uri,
64
+
queryFn: () => channelQueryFn(membershipQueryResult.atUri),
65
+
staleTime: DEFAULT_STALE_TIME,
69
+
const handshakeQueries = useQueries({
70
+
queries: channelsQueries
71
+
.map((queryResult) =>
74
+
queryKey: ["handshakes", queryResult.data.name],
75
+
queryFn: async () => {
76
+
const { routeThrough } = queryResult.data;
77
+
const latticeAtUri = stringToAtUri(
80
+
if (!latticeAtUri.ok) {
82
+
"Lattice AT URI did not resolve properly",
87
+
"Something went wrong while initiating handshakes",
90
+
// TODO: better validation
91
+
const did = latticeAtUri.data.authority as Did;
92
+
const handshakeResult = await initiateHandshakeTo(
96
+
membershipsQuery.data?.map(
98
+
queryResult.membership,
104
+
if (!handshakeResult.ok) {
109
+
handshakeResult.error,
111
+
throw new Error("Handshake failed.");
116
+
sessionInfo: handshakeResult.data,
119
+
staleTime: DEFAULT_STALE_TIME,
123
+
.filter((query) => query !== undefined),
126
+
const isInitialising =
127
+
membershipsQuery.isLoading ||
128
+
channelsQueries.some((q) => q.isLoading) ||
129
+
handshakeQueries.some((q) => q.isLoading);
132
+
membershipsQuery.error ??
133
+
channelsQueries.find((q) => q.error)?.error ??
134
+
handshakeQueries.find((q) => q.error)?.error ??
137
+
const sessions = useMemo(() => {
138
+
const sessionsMap = new Map<Did, LatticeSessionInfo>();
139
+
handshakeQueries.forEach((queryResult) => {
140
+
if (queryResult.data) {
141
+
const { did, sessionInfo } = queryResult.data;
142
+
sessionsMap.set(did, sessionInfo);
145
+
return sessionsMap;
146
+
}, [handshakeQueries]);
148
+
const value: LatticeSessionContextValue = {
152
+
getSession: (latticeDid) => sessions.get(latticeDid),
156
+
<LatticeSessionsContext value={value}>
158
+
</LatticeSessionsContext>
162
+
const membershipQueryFn = async (session: OAuthSession) => {
163
+
const memberships = await getMembershipRecordsFromPds({
164
+
pdsEndpoint: session.serverMetadata.issuer,
168
+
if (!memberships.ok) {
169
+
console.error("getMembershipRecordsFromPds error.", memberships.error);
171
+
`Something went wrong while getting the user's membership records.}`,
174
+
const { data } = memberships;
175
+
const membershipAtUris = data.map((membership) => {
176
+
const convertResult = stringToAtUri(membership.channel.uri);
177
+
if (!convertResult.ok) {
179
+
"Could not convert",
181
+
"into at:// URI object.",
182
+
convertResult.error,
186
+
return { membership, atUri: convertResult.data };
189
+
return membershipAtUris.filter((membership) => membership !== undefined);
192
+
// FIXME: holy shit don't do this. we will build prism and use that to resolve
193
+
// our memberships into channels. for now we resolve the at-uri manually.
194
+
const channelQueryFn = async (membership: AtUri) => {
195
+
const res = await getRecordFromFullAtUri(membership);
197
+
console.error("Could not retrieve record from full at uri", res.error);
198
+
throw new Error("Something went wrong while fetching channel records");
204
+
} = systemsGmstnDevelopmentChannelRecordSchema.safeParse(res.data);
208
+
"did not resolve to a valid channel record",
211
+
throw new Error("Something went wrong while fetching channel records");