ai / client /src /hooks /AuthContext.tsx
Marco Beretta
LibreChat upload repo
3b6afc0
raw
history blame
No virus
5.3 kB
import {
useState,
useEffect,
useMemo,
ReactNode,
useCallback,
createContext,
useContext,
} from 'react';
import {
TUser,
TLoginResponse,
setTokenHeader,
useLoginUserMutation,
useLogoutUserMutation,
useGetUserQuery,
useRefreshTokenMutation,
TLoginUser,
} from '@librechat/data-provider';
import { useNavigate } from 'react-router-dom';
export type TAuthContext = {
user: TUser | undefined;
token: string | undefined;
isAuthenticated: boolean;
error: string | undefined;
login: (data: TLoginUser) => void;
logout: () => void;
};
export type TUserContext = {
user?: TUser | undefined;
token: string | undefined;
isAuthenticated: boolean;
redirect?: string;
};
export type TAuthConfig = {
loginRedirect: string;
};
//@ts-ignore - index expression is not of type number
window['errorTimeout'] = undefined;
const AuthContext = createContext<TAuthContext | undefined>(undefined);
const AuthContextProvider = ({
authConfig,
children,
}: {
authConfig: TAuthConfig;
children: ReactNode;
}) => {
const [user, setUser] = useState<TUser | undefined>(undefined);
const [token, setToken] = useState<string | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const navigate = useNavigate();
const loginUser = useLoginUserMutation();
const logoutUser = useLogoutUserMutation();
const userQuery = useGetUserQuery({ enabled: !!token });
const refreshToken = useRefreshTokenMutation();
// This seems to prevent the error flashing issue
const doSetError = (error: string | undefined) => {
if (error) {
console.log(error);
// set timeout to ensure we don't get a flash of the error message
window['errorTimeout'] = setTimeout(() => {
setError(error);
}, 400);
}
};
const setUserContext = useCallback(
(userContext: TUserContext) => {
const { token, isAuthenticated, user, redirect } = userContext;
if (user) {
setUser(user);
}
setToken(token);
//@ts-ignore - ok for token to be undefined initially
setTokenHeader(token);
setIsAuthenticated(isAuthenticated);
if (redirect) {
navigate(redirect, { replace: true });
}
},
[navigate],
);
const getCookieValue = (key: string) => {
let keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return keyValue ? keyValue[2] : null;
};
const login = (data: TLoginUser) => {
loginUser.mutate(data, {
onSuccess: (data: TLoginResponse) => {
const { user, token } = data;
setUserContext({ token, isAuthenticated: true, user, redirect: '/chat/new' });
},
onError: (error) => {
doSetError((error as Error).message);
navigate('/login', { replace: true });
},
});
};
const logout = () => {
document.cookie.split(';').forEach((c) => {
document.cookie = c
.replace(/^ +/, '')
.replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
});
logoutUser.mutate(undefined, {
onSuccess: () => {
setUserContext({
token: undefined,
isAuthenticated: false,
user: undefined,
redirect: '/login',
});
},
onError: (error) => {
doSetError((error as Error).message);
},
});
};
useEffect(() => {
if (userQuery.data) {
setUser(userQuery.data);
} else if (userQuery.isError) {
doSetError((userQuery?.error as Error).message);
navigate('/login', { replace: true });
}
if (error && isAuthenticated) {
doSetError(undefined);
}
if (!token || !isAuthenticated) {
const tokenFromCookie = getCookieValue('token');
if (tokenFromCookie) {
setUserContext({ token: tokenFromCookie, isAuthenticated: true, user: userQuery.data });
} else {
navigate('/login', { replace: true });
}
}
}, [
token,
isAuthenticated,
userQuery.data,
userQuery.isError,
userQuery.error,
error,
navigate,
setUserContext,
]);
// const silentRefresh = useCallback(() => {
// refreshToken.mutate(undefined, {
// onSuccess: (data: TLoginResponse) => {
// const { user, token } = data;
// setUserContext({ token, isAuthenticated: true, user });
// },
// onError: error => {
// setError(error.message);
// }
// });
//
// }, [setUserContext]);
// useEffect(() => {
// if (token)
// silentRefresh();
// }, [token, silentRefresh]);
// Make the provider update only when it should
const memoedValue = useMemo(
() => ({
user,
token,
isAuthenticated,
error,
login,
logout,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[user, error, isAuthenticated, token],
);
return <AuthContext.Provider value={memoedValue}>{children}</AuthContext.Provider>;
};
const useAuthContext = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuthContext should be used inside AuthProvider');
}
return context;
};
export { AuthContextProvider, useAuthContext };