-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathConvexAuthState.tsx
140 lines (128 loc) · 3.92 KB
/
ConvexAuthState.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import React, {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { AuthTokenFetcher } from "../browser/sync/client.js";
import { ConvexProvider } from "./client.js";
// Until we can import from our own entry points (requires TypeScript 4.7),
// just describe the interface enough to help users pass the right type.
type IConvexReactClient = {
setAuth(
fetchToken: AuthTokenFetcher,
onChange: (isAuthenticated: boolean) => void,
): void;
clearAuth(): void;
};
/**
* Type representing the state of an auth integration with Convex.
*
* @public
*/
export type ConvexAuthState = {
isLoading: boolean;
isAuthenticated: boolean;
};
const ConvexAuthContext = createContext<ConvexAuthState>(undefined as any);
/**
* Get the {@link ConvexAuthState} within a React component.
*
* This relies on a Convex auth integration provider being above in the React
* component tree.
*
* @returns The current {@link ConvexAuthState}.
*
* @public
*/
export function useConvexAuth(): {
isLoading: boolean;
isAuthenticated: boolean;
} {
const authContext = useContext(ConvexAuthContext);
if (authContext === undefined) {
throw new Error(
"Could not find `ConvexProviderWithAuth` (or `ConvexProviderWithClerk` " +
"or `ConvexProviderWithAuth0`" + "or `ConvexProviderWithDescope`) " +
"as an ancestor component. This component may be missing, or you " +
"might have two instances of the `convex/react` module loaded in your " +
"project.",
);
}
return authContext;
}
/**
* A replacement for {@link ConvexProvider} which additionally provides
* {@link ConvexAuthState} to descendants of this component.
*
* Use this to integrate any auth provider with Convex. The `useAuth` prop
* should be a React hook that returns the provider's authentication state
* and a function to fetch a JWT access token.
*
* See [Custom Auth Integration](https://docs.convex.dev/auth/custom-auth) for more information.
*
* @public
*/
export function ConvexProviderWithAuth({
children,
client,
useAuth,
}: {
children?: ReactNode;
client: IConvexReactClient;
useAuth: () => {
isLoading: boolean;
isAuthenticated: boolean;
fetchAccessToken: (args: {
forceRefreshToken: boolean;
}) => Promise<string | null>;
};
}) {
const { isLoading, isAuthenticated, fetchAccessToken } = useAuth();
const [isConvexAuthenticated, setIsConvexAuthenticated] = useState<
boolean | null
>(null);
useEffect(() => {
let isThisEffectRelevant = true;
async function setToken() {
client.setAuth(fetchAccessToken, (isAuthenticated) => {
if (isThisEffectRelevant) {
setIsConvexAuthenticated(isAuthenticated);
}
});
}
if (isAuthenticated) {
void setToken();
return () => {
isThisEffectRelevant = false;
// If we haven't finished fetching the token by now
// we shouldn't transition to a loaded state
setIsConvexAuthenticated((isConvexAuthenticated) =>
isConvexAuthenticated ? false : null,
);
client.clearAuth();
};
}
}, [isAuthenticated, fetchAccessToken, isLoading, client]);
// If the useAuth went back to the loading state (which is unusual but possible)
// reset the Convex auth state to null so that we can correctly
// transition the state from "loading" to "authenticated"
// without going through "unauthenticated".
if (isLoading && isConvexAuthenticated !== null) {
setIsConvexAuthenticated(null);
}
if (!isLoading && !isAuthenticated && isConvexAuthenticated !== false) {
setIsConvexAuthenticated(false);
}
return (
<ConvexAuthContext.Provider
value={{
isLoading: isConvexAuthenticated === null,
isAuthenticated: isAuthenticated && (isConvexAuthenticated ?? false),
}}
>
<ConvexProvider client={client as any}>{children}</ConvexProvider>
</ConvexAuthContext.Provider>
);
}