import { useCallback, useState, useMemo, useEffect, } from 'react' import { useHistory } from 'react-router' import type { User } from 'oidc-client' import { UserManager } from 'oidc-client' import isString from 'lodash/isString' import isBoolean from 'lodash/isBoolean' import { PAGES } from 'config' import { addLanguageUrlParam, } from 'helpers/languageUrlParam' import { writeToken, removeToken } from 'helpers/token' import { setCookie, removeCookie } from 'helpers/cookie' import { isMatchPage, isMatchPageRFEF } from 'helpers/isMatchPage' import { useLocalStore, useToggle } from 'hooks' import { useLexicsStore } from 'features/LexicsStore' import { queryParamStorage } from 'features/QueryParamsStorage' import { getUserInfo } from 'requests/getUserInfo' import { checkDevice, FailedResponse } from 'requests/checkDevice' import { getClientSettings, needCheckNewDeviсe } from '../helpers' export const useAuth = () => { const { changeLang, lang } = useLexicsStore() const history = useHistory() const { close: markUserLoaded, isOpen: loadingUser, } = useToggle(true) const [user, setUser] = useState() const [isNewDeviceLogin, setIsNewDeviceLogin] = useState(false) const userManager = useMemo(() => new UserManager(getClientSettings()), []) const login = useCallback(async () => ( userManager.signinRedirect({ extraQueryParams: { lang } }) ), [userManager, lang]) const logout = useCallback(() => { userManager.clearStaleState() userManager.createSigninRequest().then(({ url }) => { const urlWithLang = addLanguageUrlParam(lang, url) userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang }) }) removeToken() removeCookie('access_token') }, [userManager, lang]) const storeUser = useCallback((loadedUser: User) => { setUser(loadedUser) writeToken(loadedUser.access_token) setCookie({ exdays: 1, name: 'access_token', value: loadedUser.access_token, }) }, []) const checkUser = useCallback(async () => { const loadedUser = await userManager.getUser() if (!loadedUser) return Promise.reject() storeUser(loadedUser) markUserLoaded() return loadedUser }, [ userManager, storeUser, markUserLoaded, ]) const [page, setPage] = useLocalStore({ clearOnUnmount: true, defaultValue: '', key: 'matchBackLocation', validator: isString, }) const [search, setSearch] = useLocalStore({ clearOnUnmount: true, defaultValue: '', key: 'searchBack', validator: isString, }) const signinRedirectCallback = useCallback(() => { userManager.signinRedirectCallback() .then((loadedUser) => { storeUser(loadedUser) queryParamStorage.clear() history.replace(PAGES.home) markUserLoaded() if (page) { const route = `${page}${page === '/' ? search : ''}` history.push(route) setPage('') setSearch('') } }).catch(login) }, [ userManager, login, storeUser, history, markUserLoaded, page, search, setPage, setSearch, ]) useEffect(() => { const isRedirectedBackFromAuthProvider = history.location.pathname === '/redirect' if (isRedirectedBackFromAuthProvider) { signinRedirectCallback() } else { checkUser().catch(() => { if (!isMatchPage() && !isMatchPageRFEF()) { login() } if (history.location.pathname === '/') { setSearch(history.location.search) } setPage(history.location.pathname) }) } }, [ checkUser, signinRedirectCallback, login, setPage, setSearch, history, ]) const reChekNewDevice = useCallback(async () => { const loadedUser = await userManager.getUser() if (!loadedUser) return checkDevice(loadedUser.access_token).catch((er:FailedResponse) => { if (er.error) return if (isBoolean(er.ok) && !er.ok) { setIsNewDeviceLogin(true) setTimeout(logout, 10000) } }) }, [logout, userManager]) const checkNewDevice = useCallback(async () => { const loadedUser = await userManager.getUser() if (!loadedUser) return checkDevice(loadedUser.access_token).catch(() => { setTimeout(reChekNewDevice, 2000) }) }, [reChekNewDevice, userManager]) useEffect(() => { if (!needCheckNewDeviсe) return undefined const startCheckDevice = setInterval(checkNewDevice, 20000) isNewDeviceLogin && clearInterval(startCheckDevice) return () => clearInterval(startCheckDevice) }, [checkNewDevice, isNewDeviceLogin, setIsNewDeviceLogin, ]) useEffect(() => { // попытаемся обновить токен используя refresh_token const tryRenewToken = () => { userManager.signinSilent().catch(logout) } // если запросы вернули 401 | 403 window.addEventListener('FORBIDDEN_REQUEST', tryRenewToken) // и если токен истек userManager.events.addAccessTokenExpired(tryRenewToken) return () => { window.removeEventListener('FORBIDDEN_REQUEST', tryRenewToken) userManager.events.removeAccessTokenExpired(tryRenewToken) } }, [userManager, logout]) useEffect(() => { // событие срабатывает после получения токена(первый // логин и обновление токена) userManager.events.addUserLoaded(storeUser) return () => userManager.events.removeUserLoaded(storeUser) }, [userManager, storeUser]) const fetchUserInfo = useCallback(async () => { const userInfoFetched = await getUserInfo() userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso) }, [changeLang]) useEffect(() => { if (user) { fetchUserInfo() } }, [fetchUserInfo, user]) const auth = useMemo(() => ({ isNewDeviceLogin, loadingUser, login, logout, user, }), [ isNewDeviceLogin, logout, user, login, loadingUser, ]) return auth }