You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
8.3 KiB
330 lines
8.3 KiB
import {
|
|
useCallback,
|
|
useState,
|
|
useMemo,
|
|
useEffect,
|
|
} from 'react'
|
|
import { useHistory } from 'react-router'
|
|
|
|
import type { User } from 'oidc-client'
|
|
|
|
import isString from 'lodash/isString'
|
|
import isBoolean from 'lodash/isBoolean'
|
|
import includes from 'lodash/includes'
|
|
|
|
import { PAGES } from 'config'
|
|
|
|
import {
|
|
addLanguageUrlParam,
|
|
writeToken,
|
|
removeToken,
|
|
readToken,
|
|
setCookie,
|
|
removeCookie,
|
|
TOKEN_KEY,
|
|
} from 'helpers'
|
|
|
|
import {
|
|
useLocalStore,
|
|
useSessionStore,
|
|
useToggle,
|
|
useEventListener,
|
|
} from 'hooks'
|
|
|
|
import { useLexicsStore } from 'features/LexicsStore'
|
|
import { queryParamStorage } from 'features/QueryParamsStorage'
|
|
|
|
import type { UserInfo, FailedResponse } from 'requests'
|
|
import {
|
|
getUserInfo,
|
|
checkDevice,
|
|
getTokenVirtualUser,
|
|
} from 'requests'
|
|
|
|
import { userManager } from '../config'
|
|
import { 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<User>()
|
|
const [isNewDeviceLogin, setIsNewDeviceLogin] = useState(false)
|
|
const [userInfo, setUserInfo] = useState<UserInfo>()
|
|
|
|
const login = useCallback(async () => {
|
|
userManager.signinRedirect({ extraQueryParams: { lang } })
|
|
}, [lang])
|
|
|
|
const logout = useCallback((key?: string) => {
|
|
setPage(history.location.pathname)
|
|
userManager.clearStaleState()
|
|
userManager.createSigninRequest().then(({ url }) => {
|
|
const urlWithLang = addLanguageUrlParam(lang, url)
|
|
userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
|
|
})
|
|
removeToken()
|
|
if (key !== 'saveToken') {
|
|
removeCookie('access_token')
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [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) {
|
|
if (!readToken()) {
|
|
const token = await getTemporaryToken()
|
|
token && await fetchUserInfo()
|
|
return Promise.resolve()
|
|
}
|
|
return Promise.reject()
|
|
}
|
|
|
|
storeUser(loadedUser)
|
|
markUserLoaded()
|
|
return loadedUser
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
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 [isFromLanding, setIsFromLanding] = useSessionStore({
|
|
clearOnUnmount: true,
|
|
defaultValue: false,
|
|
key: 'isFromLanding',
|
|
validator: isBoolean,
|
|
})
|
|
|
|
const [landingUrlFrom, setLandingUrlFrom] = useSessionStore({
|
|
clearOnUnmount: true,
|
|
defaultValue: '',
|
|
key: 'landingUrlFrom',
|
|
validator: isString,
|
|
})
|
|
|
|
const getTemporaryToken = async () => {
|
|
try {
|
|
const { access_token } = await getTokenVirtualUser()
|
|
|
|
writeToken(access_token)
|
|
setCookie({
|
|
exdays: 1,
|
|
name: 'access_token',
|
|
value: access_token,
|
|
})
|
|
return access_token
|
|
// eslint-disable-next-line no-empty
|
|
} catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
const signinRedirectCallback = useCallback(() => {
|
|
userManager.signinRedirectCallback()
|
|
.then((loadedUser) => {
|
|
storeUser(loadedUser)
|
|
queryParamStorage.clear()
|
|
if (page.includes(PAGES.useraccount)) {
|
|
history.push(PAGES.home)
|
|
} else {
|
|
const route = `${page}${search}`
|
|
history.push(route)
|
|
}
|
|
markUserLoaded()
|
|
setPage('')
|
|
setSearch('')
|
|
// }
|
|
}).catch(login)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
login,
|
|
storeUser,
|
|
markUserLoaded,
|
|
])
|
|
|
|
useEventListener({
|
|
callback: useCallback(async (e: StorageEvent) => {
|
|
const loadedUser = await userManager.getUser()
|
|
|
|
if (
|
|
e.storageArea !== localStorage
|
|
|| e.key !== TOKEN_KEY
|
|
|| !e.newValue
|
|
|| !loadedUser
|
|
|| loadedUser.access_token === e.newValue
|
|
) return
|
|
|
|
userManager.storeUser({
|
|
...loadedUser,
|
|
access_token: e.newValue,
|
|
toStorageString: loadedUser.toStorageString,
|
|
})
|
|
}, []),
|
|
event: 'storage',
|
|
})
|
|
|
|
useEffect(() => {
|
|
const isRedirectedBackFromAuthProvider = history.location.pathname === '/redirect'
|
|
isRedirectedBackFromAuthProvider ? signinRedirectCallback() : checkUser()
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
checkUser,
|
|
signinRedirectCallback,
|
|
login,
|
|
])
|
|
|
|
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])
|
|
|
|
const checkNewDevice = useCallback(async () => {
|
|
const loadedUser = await userManager.getUser()
|
|
if (!loadedUser) return
|
|
|
|
checkDevice(loadedUser.access_token).catch(() => {
|
|
setTimeout(reChekNewDevice, 5000)
|
|
})
|
|
}, [reChekNewDevice])
|
|
|
|
useEffect(() => {
|
|
if (!needCheckNewDeviсe && !user) return undefined
|
|
const startCheckDevice = setInterval(checkNewDevice, 20000)
|
|
isNewDeviceLogin && clearInterval(startCheckDevice)
|
|
return () => clearInterval(startCheckDevice)
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
checkNewDevice,
|
|
isNewDeviceLogin,
|
|
setIsNewDeviceLogin,
|
|
])
|
|
|
|
useEffect(() => {
|
|
// попытаемся обновить токен используя refresh_token
|
|
const tryRenewToken = () => {
|
|
const tokenLastUpdated = Number(localStorage.getItem('token_updated'))
|
|
// предотвращаем одновременное обновление токена в разных окнах/вкладках
|
|
const needRenewToken = Date.now() - tokenLastUpdated >= 2 * 1e3
|
|
|
|
if (!needRenewToken) return
|
|
|
|
localStorage.setItem('token_updated', String(Date.now()))
|
|
|
|
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)
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [logout])
|
|
|
|
useEffect(() => {
|
|
// событие срабатывает после получения токена(первый
|
|
// логин и обновление токена)
|
|
userManager.events.addUserLoaded(storeUser)
|
|
return () => userManager.events.removeUserLoaded(storeUser)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [userManager, storeUser])
|
|
|
|
const fetchUserInfo = useCallback(async () => {
|
|
try {
|
|
const userInfoFetched = await getUserInfo()
|
|
|
|
setUserInfo(userInfoFetched)
|
|
|
|
if (includes(window.location.pathname, PAGES.landing) && readToken()) {
|
|
changeLang(navigator.language.substring(0, 2))
|
|
} else {
|
|
userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso)
|
|
}
|
|
|
|
// eslint-disable-next-line no-empty
|
|
} catch (error) {}
|
|
}, [changeLang])
|
|
|
|
useEffect(() => {
|
|
readToken() && fetchUserInfo()
|
|
}, [fetchUserInfo, user])
|
|
|
|
const auth = useMemo(() => ({
|
|
fetchUserInfo,
|
|
isFromLanding,
|
|
isNewDeviceLogin,
|
|
landingUrlFrom,
|
|
loadingUser,
|
|
login,
|
|
logout,
|
|
page,
|
|
setIsFromLanding,
|
|
setLandingUrlFrom,
|
|
setPage,
|
|
setSearch,
|
|
setUserInfo,
|
|
user,
|
|
userInfo,
|
|
}), [
|
|
fetchUserInfo,
|
|
isNewDeviceLogin,
|
|
logout,
|
|
user,
|
|
userInfo,
|
|
landingUrlFrom,
|
|
login,
|
|
loadingUser,
|
|
setSearch,
|
|
setPage,
|
|
setUserInfo,
|
|
page,
|
|
setIsFromLanding,
|
|
setLandingUrlFrom,
|
|
isFromLanding,
|
|
])
|
|
|
|
return auth
|
|
}
|
|
|