File size: 2,315 Bytes
ec8acdf | 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 | import type { UserResource } from '@clerk/types';
export type ClerkUserState = { user: UserResource | null; isLoaded: boolean; signedIn: boolean };
export type ClerkUserStateUpdate = ClerkUserState | ((current: ClerkUserState) => ClerkUserState);
export type ClerkUserStateSetter = (update: ClerkUserStateUpdate) => void;
export type ClerkUserStateSource = {
user: UserResource | null;
addListener(cb: () => void): () => void;
};
type ClerkUserStateSyncDeps = {
hasLiveClientSession(): boolean;
subscribeClerkLoaded(cb: (clerk: ClerkUserStateSource) => void): () => void;
scheduleClerkLoad(): Promise<ClerkUserStateSource> | null;
onLoadError(err: unknown): void;
};
function applyClientSessionBaseline(current: ClerkUserState, signedIn: boolean): ClerkUserState {
return current.user === null && current.isLoaded === true && current.signedIn === signedIn
? current
: { user: null, isLoaded: true, signedIn };
}
function stateFromClerk(clerk: ClerkUserStateSource): ClerkUserState {
const user = clerk.user ?? null;
return { user, isLoaded: true, signedIn: !!user };
}
export function startClerkUserStateSync(
setState: ClerkUserStateSetter,
deps: ClerkUserStateSyncDeps
): () => void {
let mounted = true;
let unsubscribeAuth: (() => void) | undefined;
const setFromClerk = (clerk: ClerkUserStateSource): void => {
if (!mounted) return;
setState(stateFromClerk(clerk));
if (!unsubscribeAuth) {
unsubscribeAuth = clerk.addListener(() => {
if (!mounted) return;
setState(stateFromClerk(clerk));
});
}
};
const signedIn = deps.hasLiveClientSession();
// subscribeClerkLoaded fires synchronously after Clerk is loaded. Queue the
// cookie baseline first so the real Clerk user wins React's batched remount.
setState((current) => applyClientSessionBaseline(current, signedIn));
const unsubscribeLoaded = deps.subscribeClerkLoaded(setFromClerk);
if (signedIn) {
const scheduledClerk = deps.scheduleClerkLoad();
if (!scheduledClerk) {
setState({ user: null, isLoaded: true, signedIn: false });
} else {
scheduledClerk.catch((err) => {
if (mounted) deps.onLoadError(err);
});
}
}
return () => {
mounted = false;
unsubscribeLoaded();
unsubscribeAuth?.();
};
}
|