|
import { |
|
useMemo, |
|
useState, |
|
useEffect, |
|
ReactNode, |
|
useCallback, |
|
createContext, |
|
useContext, |
|
} from 'react'; |
|
import { |
|
TUser, |
|
TLoginResponse, |
|
setTokenHeader, |
|
useLoginUserMutation, |
|
useLogoutUserMutation, |
|
useGetUserQuery, |
|
useRefreshTokenMutation, |
|
TLoginUser, |
|
} from 'librechat-data-provider'; |
|
import { TAuthConfig, TUserContext, TAuthContext, TResError } from '~/common'; |
|
import { useNavigate } from 'react-router-dom'; |
|
import useTimeout from './useTimeout'; |
|
|
|
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(); |
|
|
|
const doSetError = useTimeout({ callback: (error) => setError(error as string | undefined) }); |
|
|
|
const setUserContext = useCallback( |
|
(userContext: TUserContext) => { |
|
const { token, isAuthenticated, user, redirect } = userContext; |
|
if (user) { |
|
setUser(user); |
|
} |
|
setToken(token); |
|
|
|
setTokenHeader(token); |
|
setIsAuthenticated(isAuthenticated); |
|
if (redirect) { |
|
navigate(redirect, { replace: true }); |
|
} |
|
}, |
|
[navigate], |
|
); |
|
|
|
const login = (data: TLoginUser) => { |
|
loginUser.mutate(data, { |
|
onSuccess: (data: TLoginResponse) => { |
|
const { user, token } = data; |
|
setUserContext({ token, isAuthenticated: true, user, redirect: '/chat/new' }); |
|
}, |
|
onError: (error: TResError | unknown) => { |
|
const resError = error as TResError; |
|
doSetError(resError.message); |
|
navigate('/login', { replace: true }); |
|
}, |
|
}); |
|
}; |
|
|
|
const logout = useCallback(() => { |
|
logoutUser.mutate(undefined, { |
|
onSuccess: () => { |
|
setUserContext({ |
|
token: undefined, |
|
isAuthenticated: false, |
|
user: undefined, |
|
redirect: '/login', |
|
}); |
|
}, |
|
onError: (error) => { |
|
doSetError((error as Error).message); |
|
setUserContext({ |
|
token: undefined, |
|
isAuthenticated: false, |
|
user: undefined, |
|
redirect: '/login', |
|
}); |
|
}, |
|
}); |
|
}, [setUserContext, doSetError, logoutUser]); |
|
|
|
const silentRefresh = useCallback(() => { |
|
refreshToken.mutate(undefined, { |
|
onSuccess: (data: TLoginResponse) => { |
|
const { user, token } = data; |
|
if (token) { |
|
setUserContext({ token, isAuthenticated: true, user }); |
|
} else { |
|
console.log('Token is not present. User is not authenticated.'); |
|
navigate('/login'); |
|
} |
|
}, |
|
onError: (error) => { |
|
console.log('refreshToken mutation error:', error); |
|
navigate('/login'); |
|
}, |
|
}); |
|
}, []); |
|
|
|
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) { |
|
silentRefresh(); |
|
} |
|
}, [ |
|
token, |
|
isAuthenticated, |
|
userQuery.data, |
|
userQuery.isError, |
|
userQuery.error, |
|
error, |
|
navigate, |
|
setUserContext, |
|
]); |
|
|
|
useEffect(() => { |
|
const handleTokenUpdate = (event) => { |
|
console.log('tokenUpdated event received event'); |
|
const newToken = event.detail; |
|
setUserContext({ |
|
token: newToken, |
|
isAuthenticated: true, |
|
user: user, |
|
}); |
|
}; |
|
|
|
window.addEventListener('tokenUpdated', handleTokenUpdate); |
|
|
|
return () => { |
|
window.removeEventListener('tokenUpdated', handleTokenUpdate); |
|
}; |
|
}, [setUserContext, user]); |
|
|
|
|
|
const memoedValue = useMemo( |
|
() => ({ |
|
user, |
|
token, |
|
isAuthenticated, |
|
error, |
|
login, |
|
logout, |
|
}), |
|
|
|
[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 }; |
|
|